An overview,

I have always wanted to have a good understanding of the principle of event distribution mechanism in Android, but I did not have the time, so I just took the opportunity of working from home to comb the source code. Generally speaking, a complete event will contain ACTION_DOWN, ACTION_MOVE and ACTION_UP events. The occurrence of ACTION_DOWN event indicates that a new event occurs. The ACTION_MOVE event means that the user moves his finger on the screen. ACTION_UP raises the finger, indicating that the event is nearing its end. Of course, in addition to these three events, there is ACTION_CANCEL, such as when a finger slides to the edge of the screen.

The event is generated from the screen and then passed through the following classes: Screen ->Activity->PhoneWindow->DecorView->ViewGroup->View. The whole process of event distribution involves the following three important methods:

(1) dispatchTouchEvent(); In the ViewGroup method is mainly used to distribute the event to the child View, if there is no child View to handle the event or no child View, then the current ViewGroup will handle the event.

(2) onInterceptTouchEvent (); This method exists only in viewGroups and is mainly used to intercept events in viewgroups. Because there are no child views in the View, you can handle events without intercepting them.

(3) the onTouchEvent (); The specific function that handles the event, which is executed in the user’s registered click listener during ACTION_UP event processing, namely the click method.

The general logic of the above three methods can be expressed by pseudo-codes as follows:

void dispatchTouchEvent(event):
    if(onInterceptTouchEvent()):
        onTouchEvent()
    else:
        child.dispatchTouchEvent();
Copy the code

That is, if the current ViewGoup does not intercept the event, it will distribute the event to a specific child View for execution, which is of course a very simple event handling logic. In addition to the above, there are:

(1) If there is no child View to handle the event, how is the event handled?

(2) If a subview handles the ACTION_DOWN event of an event, how can subsequent events be directly passed to it? And so on.

The above series of problems will be peeled away step by step in the process of source code parsing. However, before explaining the specific source code, we also need to understand how events are rendered in Android, which is conducive to the understanding of subsequent source code. Android uses a 32-bit integer to represent a TouchEvent event. The first 16 bits are not used. The lower 8 bits of the last 16 bits indicate the specific action (such as down, Up, and Move) of the Touch, and the higher 8 bits indicate the index value of multi-touch in the Touch event. There are three ways to get the event type or multi-touch index in normal development:

(1) getAction() : In single touch, we can directly obtain the event type through this method, but in multi-touch, it also contains the index value of multi-touch. We can get the specific event type by bit operation;

(2) getAcionMasked() : used to retrieve specific event types;

(3) getActionIndex() : Used to get the index value of multi-touch.

Second, source code analysis

1, an overview of the

In the Android Framework, events generated from the screen are first passed to the Activity and then to the PhoneWindow for processing, The event is then passed to the DecorView and passed by the ViewGroup to the parent ViewGroup for interception, distribution, and processing. If all views do not handle the event, the event will eventually be digested by the Activity. If the Activity does not handle the event either, Then the event simply disappears. Therefore, we can parse the entire event processing step by step, starting with the Activity.

2, Activity event processing

Public Boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == motionEvent.action_down) { OnUserInteraction () can be implemented if you want to know that the user is interacting with the device in some way while the Activity is in the foreground; } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }Copy the code

The Activity does nothing to the event but forwards it to the Window object, which in this case is an instance of PhoneWindow (as you can see in the Attach method when the Activity initializes its data).

3. PhoneWindow event processing

We know that the Window object in the Activity is a PhoneWindow so let’s look directly at the event handling in a PhoneWindow.

public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
Copy the code

This method is simpler and forwards the event directly to the DecorView for processing, so we go directly to the DecorView.

DecorView event handling

public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb ! = null && ! mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }Copy the code

It is not clear why there is a window. Callback. If there is no window. Callback, the event is passed to the DecorView’s parent ViewGroup. The parent class of the DecorView is FrameLayout, which in turn inherits the ViewGroup. And if you look at the source code you can see that FrameLayout does not override the dispatchTouchEvent method. So of course the event is passed to the ViewGroup’s dispatchTouchEvent method, which is where we’re going to focus.

ViewGroup event processing logic

The dispatchTouchEvent method in this class can be roughly divided into three parts :(1) whether to intercept the event processing judgment; (2) Look for a sub-view that can handle the event. If there is such a sub-view, then distribute the event to the sub-view and stop the search; (3) If no sub-view processing event exists, the current ViewGroup consumes the event; otherwise, all subsequent events are sent to the sub-View for processing. We select these parts of the core source code to explain.

5.1 Determine whether event interception is required

If the window is blocked, the event is not processed. It then determines whether the current event is an ACTION_DOWN event and reinitializes all relevant state values if so. Then determine whether the current ViewGroup intercepts events, source code as follows:

final boolean intercepted; / / if the current is ACTION_DOWN event or the last child View down issues processing, then determine whether to intercept the if (actionMasked = = MotionEvent. ACTION_DOWN | | mFirstTouchTarget! Final Boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT)! = 0; if (! intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false; } } else { intercepted = true; }Copy the code

5.2. Distribute events to subviews

The judgment of multi-touch is also involved in the event distribution process. Because the event distribution process is only sorted out here, the multi-touch related information is directly skipped…….

// If the event is not currently cancelled and the current ViewGroup does not intercept the event, decide whether to pass the event to the child View if (! canceled && ! Intercepted) {/ / whether to distribute the event to the specified View View childWithAccessibilityFocus = ev. IsTargetAccessibilityFocus ()? findChildWithAccessibilityFocus() : null; / / in ACTION_DOWN event coming son will distribute events to View the if (actionMasked = = MotionEvent. ACTION_DOWN / / refers to touch more | | (split && actionMasked = = MotionEvent. ACTION_POINTER_DOWN) / / mouse | | actionMasked = = MotionEvent. ACTION_HOVER_MOVE) {... final int childrenCount = mChildrenCount; If (newTouchTarget == null && childrenCount! = 0) {... for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); / / find the specified View if can handle the event (childWithAccessibilityFocus! = null) { if (childWithAccessibilityFocus ! = child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // If the current View is visible and no animation is playing, the event's coordinates fall within the View's coordinates. // If either of the above conditions is not met, discard the current View and continue through the next if (! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }... // Distribute events to the current child View, If return true son said that the current View consumption if the event (dispatchTransformedTouchEvent (ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); . mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // Wrap the current child View as a TouchTarget object and make the mFirstTouchTarget variable point to it idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; // The event is handled by a child View. } / / the event already has the child View for processing, so identify a particular child View the events can be handled ev. SetTargetAccessibilityFocus (false); }... }... }}Copy the code

If the current event is assigned to a particular View, that particular child View will be found. If the current event is assigned to a particular View, that particular child View will be found. If the current event is assigned to a particular View, that particular child View will be found; otherwise, the first child View that meets the following two conditions will be found.

(1) Visible and no animation played;

(2) The coordinate of the current event point is within the scope of the current sub-view;

If a View that meets the above two conditions handles the event, subsequent move and UP events are distributed to the View for processing and the lookup is stopped. Otherwise, continue to find the child View that meets the criteria to process the event. When looking for to meet the conditions after son View will pass through the method dispatchTransformedTouchEvent, follow-up and explain the method.

5.3. No child View processes events or forwards subsequent events to the same child View

If the current event is not handled by a child View, or if the current ViewGroup intercepts the current event, then the event will be consumed by the current ViewGroup. Instead, subsequent events from the current event are sent to the child View that handled the current ACTION_DOWN event (in short, an event is consumed by the same View). Of course, there is multi-touch logic in this part, so I’ll skip it for now.

// No child View handles the event or the current ViewGroup blocks the event, Then the current ViewGroup handle events if (mFirstTouchTarget = = null) {handled = dispatchTransformedTouchEvent (ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target ! = null) { final TouchTarget next = target.next; / / the current is ACTION_DOWN events and child View handles the event if (alreadyDispatchedToNewTouchTarget && target = = newTouchTarget) {handled = true; } else {/ / determine whether cancellation event distribution to child View final Boolean cancelChild = resetCancelNextUpFlag (child) target. | | intercepted. / / the last time to distribute the next event to receive the child View if (dispatchTransformedTouchEvent ACTION_DOWN events (child, ev, cancelChild, target. target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; }}Copy the code

Event final distributed in dispatchTransformedTouchEvent method, let’s take a look at the method.

5.4, dispatchTransformedTouchEvent

In this method, first determine whether the event needs to be cancelled, then determine whether there is a child View to handle the event, if so, the event is distributed to the specific child View; If not, the current ViewGroup processes the event.

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); / / cancel the event if the cancel | | oldAction = = MotionEvent. ACTION_CANCEL) {event. SetAction (MotionEvent. ACTION_CANCEL); If (child == null) {handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } final int oldPointerIdBits = event.getPoInteridbits (); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; if (newPointerIdBits == 0) { return false; } // If there is no child View to handle the event, then the current ViewGroup handles the event. Final MotionEvent transformedEvent; Final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } transformedEvent.recycle(); return handled; }Copy the code

While that’s pretty much all the ViewGroup event distribution has done so far, it’s not complicated to break the code into smaller pieces. Review what is done when the event is sent to the dispatchTouchEvent of the ViewGroup:

(1) Determine whether the window where the current View is located is blocked by other Windows. If it is blocked, the current ViewGroup has no right to process this event;

(2) If it is an ACTION_DOWN event, all previous states will be cleared first;

(3) Determine whether the current ViewGroup intercepts the current event;

(4) If the current event is not intercepted and the current event is not cancelled, then from back to front traversal all the sub-views in the ViewGroup, and find the first sub-view that meets the conditions to process the event;

(5) If there is a sub-view to process the event, the search ends; If no child View handles the current event, the current ViewGroup handles the event.

(6) Distribute subsequent ACTION_DOWN events to the same sub-view for processing. If a subsequent ACTION_MOVE or ACTION_UP event is blocked by the current ViewGroup, the event will be cancelled and will not be processed by the current ViewGroup.

In dispatchTransformedTouchEvent approach, both ViewGroup or child View processing events, eventually will be called to View the dispatchTouchEvent method, also is the final event handling is conducted in View, So let’s sort out the event handling logic in the View.

View event processing logic

Reading the dispatchTouchEvent method in the View shows that there isn’t a lot of code and the logic isn’t complicated. Currently we are concerned with the following code parts:

  //用户如果设置了onTouchEvent相关的listener,则将该事件传递给该listener进行处理
  //否则传递给当前View的onTouchEvent方法进行处理
  public boolean dispatchTouchEvent(MotionEvent event) {
    .............
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    .........
    return result;
}
Copy the code

The final processing of the event is in the View’s onTouchEvent method. If we have our own logic to handle the event, we can override the onTouchEvent method to implement the desired logic. Let’s look at some of the code in onTouchEvent.

public boolean onTouchEvent(MotionEvent event) { ........... // If Viwe is currently unavailable but clickable, Then the View can still consume the event if ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == motionEvent.action_UP && (mPrivateFlags & PFLAG_PRESSED) ! = 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; return clickable; }... // If the current View is clickable or the View is of type tooltip, So will consume the event / / so the next step is to ACTION_DOWN ACTION_MOVE ACTION_UP and several ACTION_CANCEL event respectively, for processing the if (clickable | | (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { ................ return true; } return false; }Copy the code

Here we omit the details of processing ACTION_DOWN, ACTION_MOVE, ACTION_UP and ACTION_CANCEL in onTouchEvent. If necessary, you can search for a lot of explanations. However, it is important to note that at the end of the ACTION_UP event processing, the performClick method is called by calling the performClickInternal method, and the user-set click event listener is called from that method. So the onTouchEvent method in the listener set by the user, the onTouchEvent method in the View, and the click event set are prioritized from high to low. If the user sets the listener associated with onTouch, the next two methods will not be executed.

Four,

Android event roughly distribution process and processing logic basically speaking from Activity to specific View is basically finished, explain the whole process of the following several issues are also included in the inside:

(1) Several classes are involved in the process of Android event distribution;

(2) Event processing logic after the ViewGroup intercepts the event;

(3) how to find the sub-view that meets the event processing conditions, and how to distribute the event to the sub-view;

(4) If a child View receives an event after the ACTION_DOWN event, how are subsequent events of the event directly distributed to it;

(5) If the event can not be found to handle the sub-view, then how the event is processed and so on.