preface

Android is more about interacting with the user than just showing static pages. Users interact with the App by touching the screen, providing a better user experience. In the App layer, we need to receive the touch events on the screen for the corresponding logical operation. This series of articles will analyze the twists and turns of the input events in the App layer. This series is divided into three articles:

1, Android input event: DecorView (1) 2, Android input event: DecorView (2

Through this article, you will learn:

Input event distribution responsibility chain 3. Touch/Key event processing 4. Root View receives events

Input events are distributed to the App layer from where

Input events are input from below

The event generated by screen touch is processed by the bottom layer and finally distributed to the corresponding Window. We need to find the bridge between the bottom layer and Window. Find the InputEventReceiver class:

InputEventReceiver.java private void dispatchInputEvent(int seq, InputEvent event) { mSeqMap.put(event.getSequenceNumber(), seq); // Handle the input event onInputEvent(event); }Copy the code

The InputEvent is InputEvent. InputEventReceiver is an abstract class that subclasses the ViewrotimPL inner class WindowInputEventReceiver:

WindowInputEventReceiver.java @Override public void onInputEvent(InputEvent event) { ... if (processedEvents ! = null) { ... } else {// Join the input queue enqueueInputEvent(event, this, 0, true); }}Copy the code

OnInputEvent: overrides the InputEventReceiver method.

App connects with the bottom layer

The underlying input event is finally sent to the WindowInputEventReceiver in view Windows PL. Take a look at the ViewRootImpl class:

ViewRootImpl.java InputChannel mInputChannel; WindowInputEventReceiver mInputEventReceiver; public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... If ((mWindowAttributes. InputFeatures & WindowManager. LayoutParams. INPUT_FEATURE_NO_INPUT_CHANNEL) = = 0) {/ / input channel is established mInputChannel = new InputChannel(); } try { ... //InputChannel establishes contact with WindowManagerService, Namely after the input event is sent to the Window res. = mWindowSession addToDisplay (mWindow mSeq, mWindowAttributes, getHostVisibility (), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); setFrame(mTmpFrame); } catch (RemoteException e) { ... } if (mInputChannel ! = null) { if (mInputQueueCallback ! = null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } // Bind the input channel to the receiver //mInputEventReceiver is the receiver, and the underlying data is sent to the receiver via the mInputChannel // The current Looper is the main thread Looper, MessageQueue mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, looper.myLooper ()); }}Copy the code

To see mWindowSession. AddToDisplay (xx) methods:

Session.java @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) {//mService is WindowManagerService object return mservice. addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel, outInsetsState); }Copy the code

Look again at the WindowManagerService addWindow method:

WindowManagerService.java
    public int addWindow(xx) {
        ...
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
        ...
    }
Copy the code

Final call:

WindowState.java void openInputChannel(InputChannel outInputChannel) { ... / / register input channel mWmService. MInputManager. RegisterInputChannel (mInputChannel, mClient asBinder ()); }Copy the code

Register a channel passed from the upper layer, so that the channel is connected to the Window. Now that we have established the connection, how do we channel the data out to the upper level? Look at the InputEventReceiver constructor:

InputEventReceiver.java public InputEventReceiver(InputChannel inputChannel, Looper looper) { ... mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); // When there is data at the bottom, it will be sent to the queue. // The final message loop is taken out and executed. // The dispatchInputEvent method of InputEventReceiver is called MReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); mCloseGuard.open("dispose"); }Copy the code

For details about Looper, see Android event driver handler-message-looper parsing

Enter the event distribution responsibility chain

Now that input events have been allocated to the App’s corresponding Window, let’s look at how to handle those events, starting with the WindowInputEventReceiver onInputEvent(xx) method. This method calls ViewRootImpl’s enqueueInputEvent(xx) method:

ViewRootImpl.java void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, Boolean processImmediately) {// Construct InputEvent as QueuedInputEvent; //QueuedInputEvent is a list enclosing InputEvent, QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); QueuedInputEvent last = mPendingInputEventTail; If (last == null) {// If (last == null) {mPendingInputEventHead = q; mPendingInputEventTail = q; } else {// Insert node at the end of queue last.mnext = q; MPendingInputEventTail = q; } // Queue count +1 mPendingInputEventCount += 1; If (processImmediately) {// Handle doProcessInputEvents() immediately; } else {/ / delay processing, sent via Handler scheduleProcessInputEvents (); }} void doProcessInputEvents() {// While loop processes all nodes in the queue while (mPendingInputEventHead! QueuedInputEvent q = mPendingInputEventHead; mPendingInputEventHead = q.mNext; if (mPendingInputEventHead == null) { mPendingInputEventTail = null; } q.mNext = null; mPendingInputEventCount -= 1; . DeliverInputEvent (q); }... } private void deliverInputEvent(QueuedInputEvent q) { ... // InputStage stage; // InputStage stage; // InputStage stage; // InputStage stage / / determine the starting position of the input chain, which is starting at a node in the chain in the if (q.s houldSendToSynthesizer ()) {stage = mSyntheticInputStage; } else {//touch event go mFirstPostImeInputStage //key event go mFirstInputStage = q.houldSkipime ()? mFirstPostImeInputStage : mFirstInputStage; }... if (stage ! = null) { handleWindowFocusChanged(); // The responsibility chain handles the event stage.Deliver (q); } else { finishInputEvent(q); }}Copy the code

The responsibility chain mentioned above, the input events are given to the responsibility chain to handle, so where is the responsibility chain established, what nodes does it have? Let’s start with the InputStage, which is an abstract class:

Abstract class InputStage {// Record a node private final InputStage mNext; Protected static final int FORWARD = 0; Protected static final int FINISH_HANDLED = 1; // Protected static final int FINISH_NOT_HANDLED = 2 has been handled; Public InputStage(InputStage Next) {mNext = next; } // Some processing methods, some overridden by subclasses}Copy the code

Continuing with the InputStage subclass, let’s go back to the ViewRootImpl setView(xx) method:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { ... // Set up the input pipeline. CharSequence counterSuffix = attrs.getTitle(); MSyntheticInputStage = new SyntheticInputStage(); // View processing after input method, The next node points to mSyntheticInputStage InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); // Local processing after input method, The next node points to viewPostImeStage InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); // The next node points to nativePostImeStage InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); // Input method processing, the next node points to earlyPostImeStage InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix) ImeStage InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); // Local processing before input method, The next node points to viewPreImeStage InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); MFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; }}}Copy the code

It can be seen that when defining the chain of responsibility, its order has been specified, as shown in the figure:

Touch/Key event processing

Inputevent. Java has two subclasses:

stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
Copy the code

Touch event select EarlyPostImeInputStage Key event select NativePreImeInputStage

After the responsibility chain node flow, it is finally processed by the responsibility chain node: ViewPostImeInputStage

ViewPostImeInputStage protected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) ! = 0) {//Touch event handler return processPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) ! = 0) { return processTrackballEvent(q); } else { return processGenericMotionEvent(q); }}}Copy the code

As you can see, the Touch/Key events are split here. processKeyEvent(xx)

private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; . //mView is a View held by View wrotimpl, If (mview.dispatchKeyevent (event)) {return FINISH_HANDLED; }... }Copy the code

processPointerEvent(xx)

Private int processPointerEvent(QueuedInputEvent q) {// Convert InputEvent to MotionEvent final MotionEvent event = (MotionEvent)q.mEvent; mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; //mView is a View held by View wrotimpl, Said the Windows of the root View / / here will be treated as MotionEvent to root View Boolean handled. = mView dispatchPointerEvent (event); . return handled ? FINISH_HANDLED : FORWARD; }Copy the code

Both KeyEvent and MotionEvent are handled by the Window Root View. The View dispatchKeyEvent and dispatchPointerEvent methods are called respectively.

The Root View receives events

A View needs to be added to a Window in order to be displayed. The method for adding a View to a Window:

public void addView(View view, ViewGroup.LayoutParams params);
Copy the code

When windowManager.addView (View View, viewGroup.layoutparams params) is used, this View is the Root View of the Window. Take a look at some common Root View activities. Root View Activities use a DecorView as the Root View

Dialog uses a DecorView as the Root View

PopupWindow uses PopupDecorView as the Root View.

Toast Root View Toast is loaded by default

View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
Copy the code

V as its default Root View of course it can also change its default Root View:

toast.setView(View rootView)
Copy the code

Dialog/PopupWindow Toast Dialog PopupWindow Toast

conclusion

According to the above analysis, input events are transmitted from the bottom layer to the App layer, processed by the responsibility chain, and finally distributed to the Root View. The process is shown in the figure: