Today’s article is shared by Cheeeelok from Xiaomi. This article mainly takes you step by step to understand the Android Input subsystem from the perspective of system source code. Today is Saturday, I don’t know how many students will read technical articles on the weekend? Feel good article, can reward or forward, article reward will be owned by the author.

The original address: https://zhuanlan.zhihu.com/p/29152319

From my personal understanding, Android Input system is actually a system-level event processing and distribution framework, and it needs functional modules roughly: event reading, event classification and event distribution. So let’s start with the Input source of the entire Input system and see how events are entered into the Input system.

Before we look at the code, let’s think about what submodules we would consider if we were to design an input read module for an event distribution framework:

  1. Event generation module (when the user performs an operation on the device, InputEvent is generated, the hardware generates an interrupt and gives the event to the driver, the driver to the kernel, and the kernel to the framework)

  2. Event listener module (much like designing a server to start a thread listener in order to respond to requests from clients in a timely manner)

  3. Event reading module

  4. Event distribution module

So now we have at least a starting point for the whole learning process, which is who is listening on the Input system, and what they’re doing when they’re listening. Before we begin, I’d like to share with you an image I drew from the content of this article:

InputManagerService Initialization overview

First, there are a few things we can agree on:

  1. Android Framework services (Java) are created by the system_server process (there is no fork, so they run in the system_server process).

  2. After a Service is created, it is managed by the ServiceManager running in the System_server process.

So the creation of InputManagerService can be found in the SystemServer startOtherServices() method, which does the following:

  1. Create the InputManagerService object

  2. Hand it over to the ServiceManager for management

  3. Register WindowManagerService’s InputMonitor with InputManagerService as a callback after the window responds to an event

  4. Start InputManagerService.

SystemServer.javastartOtherServices(a){
    ……
    inputManager = newInputManagerService(context); ... inputManager.setWindowManagerCallbacks(wm.getInputMonitor()); inputManager.start(); ... }Copy the code

Then we will learn the corresponding processing part by part.

Creation of the InputManagerService object

Creating the InputManagerService object does the following:

  1. Create a Handler responsible for processing messages in the DisplayThread thread

  2. NativeInit is called to initialize the InputManagerService of the native layer, passing in the DisplayThread message queue during initialization

  3. Save native layer InputManagerService with mPtr

  4. After initialization, the Service is added to the LocalServices and stored as key-value pairs through a Map

InputManagerService.javapublic InputManagerService(Context context) {    this.mContext = context;    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

    mUseDevInputEventForAudioJack =
            context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
    Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
            + mUseDevInputEventForAudioJack);
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

    LocalServices.addService(InputManagerInternal.class, new LocalService());
}Copy the code

Now, one might ask, why does InputManagerService bind to DisplayThread? Since InputEvent is retrieved, sorted, and distributed, it will still be processed, which means that its results will be displayed on the UI, InputManagerService will naturally choose threads that are closer to the UI.

InputManagerService is bound to InputManagerService, which runs on its own main thread. All of this is too inefficient, but instead, could it be tied to a thread that manages or is very close to all of your application’s UI?

I’m not going to say what the answer is, but you can use your own knowledge to figure it out.

Initialize the InputManagerService of the native layer

In the nativeInit function, the Java layer MessageQueue is converted to the Native layer MessageQueue, and then Looper is extracted for the initialization of the NativeInputManager. The highlight here is the creation of the NativeInputManager, which does the following:

  1. Converting Java layer Context and InputManagerService to native layer Context and InputManagerService are stored in mContextObj and mServiceObj

  2. Initialize a variable

  3. Create EventHub

  4. Create a InputManager

com_android_server_input_InputManagerService.cpp

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mServiceObj = env->NewGlobalRef(serviceObj);

    {        AutoMutex _l(mLock);
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
    }
    mInteractive = true;

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this.this);
}Copy the code

EventHub

A lot of people look at this and think, what’s EventHub? In English, it means the hub of events. As we mentioned at the beginning of this article, events in the Input system originate from the driver/kernel, so we can assume that EventHub is the hub for handling meta-events from the driver/kernel. Let’s verify our ideas in the source code.

The creation of EventHub does the following:

  1. Create mEpollFd to listen for data (event or not) to read

  2. Create a mINotifyFd, register it with the DEVICE_PATH (in this case /dev/input) node, and hand it to the kernel to listen for events on the device node. Epoll_wait () returns whenever an event is added or deleted, allowing EventHub to receive notification from the system and obtain details about the event

  3. Call the epoll_ctl function to register mEpollFd and mINotifyFd with epoll

  4. Define int wakeFd[2] as the read and write ends of the event pipeline and register the read ends with ePoll for mEpollFd to listen on

EventHub.cpp EventHub::EventHub(void) : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(), mOpeningDevices(0), mClosingDevices(0), mNeedToSendFinishedDeviceScan(false), mNeedToReopenDevices(false), mNeedToScanDevices(true), mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) { acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); mEpollFd = epoll_create(EPOLL_SIZE_HINT); LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); mINotifyFd = inotify_init(); int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE); ... result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); ... int wakeFds[2]; result = pipe(wakeFds); ... mWakeReadPipeFd = wakeFds[0]; mWakeWritePipeFd = wakeFds[1]; result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); ... result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); ... result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem); ... }Copy the code

So this raises the question: why register the read end of a pipe with epoll? How do we wake EventHub if EventHub is blocked in epoll_wait() because getEvents do not read the event and we do not bind the read side? If we bind the read side of the pipe, we can wake up EventHub by writing data to the write side of the pipe.

The creation of a InputManager

Creating an InputDispatcher object to distribute events, an InputReader object to read events and deliver them to the InputDispatcher for distribution, We then call Initialize () to initialize the InputReaderThread and InputDispatcherThread.

InputManager.cpp

InputManager::InputManager(        const sp<EventHubInterface>& eventHub,        const sp<InputReaderPolicyInterface>& readerPolicy,        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}Copy the code

Both InputDispatcher and InputReader are relatively simple to create. InputDispatcher creates its own thread Looper and sets distribution rules based on incoming dispatchPolicy. InputReader encapsulates the incoming InputDispatcher as a listener and stores it, doing some data initialization.

At this point, the initialization of the InputManagerService object is complete, and the start() method of InputManagerService is called.

Listen for the start of the InputReader and InputDispatcher threads

In the start() method, the following is done:

  1. Calling the nativeStart method is essentially calling the Start () method of InputManager

  2. Submit the InputManagerService to the WatchDog

  3. Register the touch speed, the observer that displays the touch, and register the broadcast to monitor them

  4. Actively call the updateXXX method to update (initialize)

InputManagerService.javapublic void start(a) {
    Slog.i(TAG, "Starting input manager");
    nativeStart(mPtr);    // Add ourself to the Watchdog monitors.
    Watchdog.getInstance().addMonitor(this);

    registerPointerSpeedSettingObserver();
    registerShowTouchesSettingObserver();
    registerAccessibilityLargePointerSettingObserver();

    mContext.registerReceiver(new BroadcastReceiver() {        @Override
        public void onReceive(Context context, Intent intent) { updatePointerSpeedFromSettings(); updateShowTouchesFromSettings(); updateAccessibilityLargePointerFromSettings(); }},new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

    updatePointerSpeedFromSettings();
    updateShowTouchesFromSettings();
    updateAccessibilityLargePointerFromSettings();
}Copy the code

The start() method of InputManager is obviously the most interesting. Unfortunately, this method is not worth our attention because it simply starts the InputDispatcherThread and InputReaderThread to listen.

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();        return result;
    }    return OK;
}Copy the code

So how does the InputReaderThread thread relate to EventHub?

For InputReadThread:

  1. MReader ->loopOnce()

  2. LoopOnce () calls mEventHub->getEvents to read the event

  3. Upon reading the event, processEventsLocked is called to process the event

  4. After processing is complete, call getInputDevicesLocked to obtain input device information

  5. Call mPolicy – > notifyInputDevicesChanged function using InputManagerService agent MSG_DELIVER_INPUT_DEVICES_CHANGED messages sent through the Handler, notify the input device has changed

  6. Finally, mQueuedListener-> Flush () is called to pass all events in the event queue to the InputDispatcher registered in the InputReader

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();    return true;
}voidInputReader: : loopOnce () {... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); {// acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();        if(count) { processEventsLocked(mEventBuffer, count); }...if(oldGeneration ! = mGeneration) { inputDevicesChanged =true; getInputDevicesLocked(inputDevices); }}// release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    ……
    mQueuedListener->flush();
}Copy the code

At this point, the learning of the event Input module of the Input system is over. In the subsequent articles, we will continue to learn the event classification and distribution process of the Input system. Interested friends can pay attention to it.

IOS rewards lead directly to Cheeeelok’s thumbs up code

More exciting, welcome to pay attention [Technology horizon