A backstage friend shared a headline interview question: What actions and events happen when you press the Home button on your mobile phone?

Today we will analyze, this article source code based on Android – 28

Classification of events

Events in The Android system are mainly as follows:

  • KeyEvent Events generated by physical keys, such as Home, Back, Volume Up, Volume Down, and Camera. And that’s the kind of thing we’re going to be analyzing today.
  • Touchevents are clicks and drags on the screen, and their combination of events.
  • MouseEvent Indicates the event generated by a mouse operation
  • TrackBallEvent The TrackBallEvent knows the trackball, but it’s not to reveal the age

Android extracts a unified abstract class called InputEvent for these common events. InputEvent provides several common abstract methods, such as getDevice() to get the “hardware source” of the current event, and getEventTime() to get the time when the event occurred.

InputEvent has two subclasses:

  • KeyEvent Describes key events
  • MotionEvent is used to describe Movement type events (generated by mouse, pen, finger, trackball).

And we want to listen for these events by setting the corresponding listener on the View

setOnKeyListener(OnKeyListener)
setOnTouchListener(OnTouchListener)
...
Copy the code

Or you can simply duplicate the related methods

boolean onKeyDown(int keyCode, KeyEvent event)
boolean onTouchEvent(MotionEvent event).Copy the code

Preparation for event processing

The whole idea of event processing design is that the driver layer will have a message queue to store events, a Reader to read events, and a Dispatcher to distribute events in the message queue. Events sent by the Dispatcher are reported to InputManagerService via JNI and then passed to PhoneWindow through the interface. PhoneWindow does different processing for different event types.

Let’s take a look at where Reader, Dispatcher comes from.

SystemServer starts InputMangerService in the startOtherServices() method

InputManagerService inputManager = new InputManagerService(context);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
Copy the code

Let’s look at the constructor of InputMangerService

public InputManagerService(Context context) {
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper()); 
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); . }Copy the code

A message queue is passed in by calling the nativeInit method, which initializes some native objects. The final goal is to create a new Native layer InputManager. Call chain such as (want to know more students can be viewed in the corresponding source class) :

   new InputManagerService() // InputManagerService.java-> nativeInit(... , queue)// com_android_server_input_InputManagerService.cpp
-> newNativeInputManager(... , looper)// com_android_server_input_InputManagerService.cpp
-> new InputManager(eventHub, ...) //InputManager.cpp      
Copy the code

So the point is in this InputManager

InputManager::InputManager(...) {
    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

We can see that in InputManager we have the mReader, mDispatcher, and two related threads ready, so of course the next step is to run the thread to read the event and distribute the event.

The call chain that starts the event reading and distributing thread is as follows:

   SystemServer.startOtherServices()	
-> inputManagerService.start()
-> nativeStart() // com_android_server_input_InputManagerService.cpp
-> InputManager.start() // InputManager.cpp
Copy the code

The final start method is very simple, just run the two threads

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
}
Copy the code

At this point, the objects and threads needed for the event processing work are ready. If you can’t remember the previous code, remember this: When the SystemServer starts IMS, it creates a native InputManager object. The InputManager reads events continuously through the mReader and dispatches events continuously through the mDispatcher.

Now let’s look at how events are read and distributed.

Event reading

InputReaderThread waits for keystroke messages to arrive. This Thread endlessly calls the loopOnce method of InputReader in threadLoop.

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
Copy the code

In the loopOnce method, EventHub is used to fetch events and put them into the buffer:

void InputReader::loopOnce() {
    // EventHub reads events from the driver
    size_tcount = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); .if (count) {
    	 // Get RawEvent, KeyEvent, MotionEvent, etc
       processEventsLocked(mEventBuffer, count);
    }
    // Flush the queue events to the listener, which is actually the InputDispatcher event dispatcher.
    mQueuedListener->flush();
}
Copy the code

InputDispatcher is called when an event is received. NotifyKey is called if it is a KeyEvent and notifyMotion is called if it is a MotionEvent

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
    
    // Do some processing with NatvieInputManager before the Event is enqueued
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/policyFlags); . KeyEntry* newEntry =new KeyEntry(args->eventTime, args->source,
                args->action, flags, keyCode,...)
    // Events go to the end of the queue
    needWake = enqueueInboundEventLocked(newEntry);
}
Copy the code

This is how InputReader gets the device event notification InputDispatcher and places it in the event queue.

Distribution of events

Here’s how InputDispatcher reads events from the event queue and dispatches them.

The dispatchOnce method is called endlessly in the threadLoop of the InputDispatcherThread

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}
Copy the code

This method has two functions:

  • Call dispatchOnceInnerLocked to distribute events;
  • Call runCommandsLockedInterruptible to handle your order, CommandQueue team and handle, until the queue is empty.
void InputDispatcher::dispatchOnce() {
     if(! haveCommandsLocked()) { dispatchOnceInnerLocked(&nextWakeupTime); }if(runCommandsLockedInterruptible()) { nextWakeupTime = LONG_LONG_MIN; }... }Copy the code

There are many types of events that are handled in dispatchOnceInnerLocked, focusing on key types (other events such as touches, device resets, etc.). After a call to PhoneWindowManager, finally back to Java,

	 InputDispatcher::dispatchOnceInnerLocked
-> dispatchKeyLocked  
-> doInterceptKeyBeforeDispatchingLockedInterruptible     
-> NativeInputManager.interceptKeyBeforeDispatching
-> PhoneWindowManager.interceptKeyBeforeDispatching  
...
     
/ / the following NativeInputManager doInterceptKeyBeforeDispatchingLockedInterruptible part of the code
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
            &event, entry->policyFlags);
if (delay < 0) {
  The // Home event will be intercepted
  entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;    
} 
// If it is not intercepted, it will be processed and distributed
Copy the code

PhoneWindowManager

No more words, continue to look at the code, not far from victory! The source code comments are written very clearly, I will not translate. Really not because of laziness, I want you to improve your English reading level. (- > < -)

public long interceptKeyBeforeDispatching(KeyEvent event, ...) {
 // First we always handle the home key here, so applications
 // can never break it, although if keyguard is on, we do let
 // it handle it, because that gives us the correct 5 second
 // timeout.
 if (keyCode == KeyEvent.KEYCODE_HOME) {
     // If we have released the home key, and didn't do anything else
     // while it was pressed, then it is time to go home!
     if(! down) { cancelPreloadRecentApps(); mHomePressed =false; .// Delay handling home if a double-tap is possible.
         if(mDoubleTapOnHomeBehavior ! = DOUBLE_TAP_HOME_NOTHING) { mHomeDoubleTapPending =true;
             mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                     ViewConfiguration.getDoubleTapTimeout());
             return -1;
         }
         handleShortPressOnHome(); / / short press

         // The result of the -1 call gives the INTERCEPT_KEY_RESULT_SKIP value
         return -1; }...// Remember that home is pressed and handle special actions.
     if (repeatCount == 0) {
         mHomePressed = true;
         if (mHomeDoubleTapPending) {
             handleDoubleTapOnHome();/ / double
         } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
             preloadRecentApps();/ / app recently}}else if((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) ! =0) {
         if(! keyguardOn) { handleLongPressOnHome(event.getDeviceId());/ / long press}}return -1; }}Copy the code

Home related events are handled here. Let’s take a look at how handleShortPressOnHome is implemented by pressing Home to enter Luncher.

private void handleShortPressOnHome(a) {...// Go home! Hang in there and we'll Go Home!
    launchHomeFromHotKey();
}
Copy the code

Go Home!

void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
    if (respectKeyguard) {
        // Handle some screen lock cases, maybe return directly
    }
    // no keyguard stuff to worry about, just launch home!
    if (mRecentsVisible) {
    	  // Delay the start of the Activity in the background to avoid interrupting the user's operation
        // The name of the stop method actually implements a delay of 5s
        ActivityManager.getService().stopAppSwitches();
        // Hide Recents and notify it to launch Home
        hideRecentApps(false.true);
    } else {
        // Otherwise, just launch Home
        startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); }}Copy the code

Finally, take a look at some of the details of jumping to Home

 void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
     ActivityManager.getService().stopAppSwitches();
     // Close system popovers, such as input methods
     sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);

     Intent dock = createHomeDockIntent();
     if(dock ! =null) { // Open the app drawer
         startActivityAsUser(dock, UserHandle.CURRENT);
         return;
     }
   
     intent = mHomeIntent // omit some logic
     // Open the Home page
     startActivityAsUser(intent, UserHandle.CURRENT);
 }
Copy the code

How to answer

Interviewer: What actions and events take place when you press the Home button on your mobile phone

🤔️ : After the Home button is pressed, the underlying driver will obtain this event. IMS reads the event captured by the driver through the Reader and distributes the event through the Dispatcher. Before Dispatcher dispatches events, PhoneWindowManager intercepts Home and other system events, where pressing the Home button is processed as follows: Close the corresponding system popup, delay any Activity to be opened, and finally open the Home or Dock page with an Intent.