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: