Android Event Distribution Mechanism-Deep Source Parsing from the Foundation

Posted by skalooky on Wed, 22 May 2019 02:43:52 +0200

Reprinted from: Basket Link Address: http://www.jianshu.com/p/e6ceb7f767d8

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Preface

Some time ago, when I was looking for a job, I read many books about the mechanism of event distribution, and Daniel analyzed them from different perspectives. I benefited greatly, so I got this analysis of the essence of heaven and earth.

This article will start with what event distribution mechanism is, and go into source code analysis.
The main purpose is to deepen their understanding, but also to make it easier for readers to understand without feeling dry.


concept

This section is the foundation, I incarnate 100,000 why to raise the following questions! If the reader knows it, jump straight to the next section!

  • What is the event distribution mechanism?
    Event distribution mechanism is the distribution of click events.

  • So what is the click event?
    The same sequence of events that occurs after a finger touches the screen is a click event.

  • What are the types of click events?

    • Fingers just touched the screen
    • Fingers sliding on the screen
    • A moment when the finger is released from the screen
  • What is the same sequence of events?
    Everything that happens from the moment your finger touches the screen to the moment your finger is released from the screen.

  • How do click events be represented in code?
    In the source code, MotionEvent is a click event, and the distribution of click events is the distribution and delivery process of MotionEvent objects.

  • What type of click event is MotionEvent?

    • ACTION_DOWN: Fingers just touched the screen
    • ACTION_MOVE: Fingers sliding on the screen
    • ACTION_UP: A moment when the finger is released from the screen
  • So how is this Motion Event delivered?
    Let's look at the next section.

Event distribution mechanism

The so-called event distribution mechanism is actually the distribution process of Motion Event.
When a Motion Event (click event) is generated, the system needs to pass it to a specific View, which is the event distribution mechanism.

1. Let's briefly describe a click event (not involving method calls, but a general system first)

  • MotionEvent (Click Event) generated by user contact screen
  • MotionEvent is always received by Activity first
  • Activity receives and passes MotionEvent (click event): Activity - > Window - > DecorView (DecorView is the underlying container of the current interface, which is the parent container of the View set by setContentView)
  • DecorView is a View Group that distributes MotionEvent (click event) to various sub-Views

2. Three methods
Believe that you already know something about click events, then we will introduce three important methods of event distribution mechanism. The mechanism of click events distribution is based on these three methods: dispatch TouchEvent (), onIntercept TouchEvent (), and onTouchEvent().

  • dispatchTouchEvent(): Used for event distribution, if MotionEvent (click event) can be passed to the View, then the method must be called. The return value is determined by the return value of onTouchEvent() itself and dispatchTouchEvent() of the child View.

    • The return value is true, which means that the click event is consumed by itself or by the sub-View.
    • The return value is false, indicating that the ViewGroup has no child elements, or that the child elements do not consume the event.
  • onInterceptTouchEvent(): Called in dispatchTouchEvent() to determine whether an event is intercepted. If the current View intercepts an event, the method will not be accessed in the same event sequence.

  • onTouchEvent(): Called in dispatchTouchEvent(), the return result indicates whether the current event is consumed or not, and if it is not consumed (returning false), View will not receive the event again in the same event sequence.


3. The relationship of the three methods
So many concepts, don't have a headache! Let's look at the relationship between the three methods with pseudo code!

public boolean dispatchTouchEvent(MotionEvent ev) {
      boolean handled = false;
        if (onInterceptTouchEvent(ev)) {
            handled = onTouchEvent(ev);
        } else {
            handled = child.dispatchTouchEvent(ev)
        }
        return handled;
}  

This pseudocode can well understand the mechanism of event delivery:
The user clicks on the screen to generate MotionEvent (click event). After receiving MotionEvent (click event), the dispatchTouchEvent() of View executes onInterceptTouchEvent() of the View to determine whether the event is intercepted. If the onTouchEvent() method of executing the View is intercepted, the dispatchTouchEvent() of the child View is called if it is not intercepted. Similar logic is used in the source code for event delivery.

4. Sequence of event delivery

  • Users click on the screen to generate MotionEvent (click event)
  • Activity receives MotionEvent - > Passes to Window - > Passes to DecorView (ViewGroup) - > Execute ViewGroup's dispatch TouchEvent ()
  • When ViewGroup receives MotionEvent, it distributes events according to the event distribution mechanism.
  • If the child View does not consume events and onTouchEvent() returns false, the event is passed back to its parent View's onTouchEvent(), and if the parent View does not consume, it is finally passed back to Activity for processing.

Generally speaking, the order of click events is from father to son, and then from son to father.

Graphical Event Transfer Mechanism

Nowadays, most articles on the Internet explain the transmission of events through source code and log. For the people who read the articles, the experience is not so good, and they can't figure it out in the fog. Here is a sunflower treasure book! Looking at this picture, mother no longer has to worry about my study!


Graphics of Event Transfer Mechanisms

Friendship Tips:

  1. Students who still don't understand can see the effect better by comparing the previous part.
  2. The onTouchEvent of View in the figure returns false and passes events to ViewGroup, not directly. It is the dispatchTouchEvent() method of the superior ViewGroup that receives the false returned by the onTouchEvent() of the child View and distributes the event to the onTouchEvent of the ViewGroup.
  3. There is no copy of onTouchEvent in ViewGroup, but ViewGroup itself is View, and onToucheEvent is in View.

Source code analysis

Having seen it for so long, let's finally come to see the source code. Not much nonsense! A library!


Right-handed left-handed to a few lines of source code

1. Activity's distribution of click events
Let's start with Activity's dispatch TouEvent, the source of all click events received

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
}

In this code, we focus on getWindow (). superDispatch TouchEvent (ev), which passes the click event to Window. The return value indicates whether the click event was consumed. If all View s do not consume click events, Activity calls its own onTouchEvent.

Let's look at the source code of Window s:

public abstract boolean superDispatchTouchEvent(MotionEvent event);

Discovering that it is actually an interface, where is the implementation method? No hurry, no difficulty to find, the top comment of the source code says

The only existing implementation of this abstract class is android.view.PhoneWindow,

The only way to implement this interface is PhoneWindow, so let's look at the source code of PhoneWindow:

private DecorView mDecor;

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

Is it very familiar? In fact, he threw the pot directly to DecorView, which, as I mentioned earlier, is the bottom container of the current interface, the parent container of the View set by setContentView. So look at DecorView:

public boolean superDispatchTouchEvent(MotionEvent event) {
     return super.dispatchTouchEvent(event);
}

Code egg! It was passed out again, this time calling super, and DecorView inherited from ViewGroup, so it called dispatch TouchEvent of ViewGroup! So let's take a look at the source code in ViewGroup first!

2.ViewGroup Distribution of Events
Let's start with a small section of dispatch TouchEvent in ViewGroup

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
          || mFirstTouchTarget != null) {
   final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
   if (!disallowIntercept) {
     intercepted = onInterceptTouchEvent(ev);
     ev.setAction(action); // restore action in case it was changed
   } else {
     intercepted = false;
   }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

Let's start from scratch. MotionEvent.ACTION_DOWN was introduced before. What is mFirstTouchTarget? The later code indicates that when the click event of the ViewGroup is consumed by the child View, the mFirstTouchTarget points to the child View. So if the event is consumed by the child View or ACTION_DOWN event, access the onInterceptTouchEvent of the ViewGroup, if not all of it is intercepted by the current ViewGroup. In other words, if the View decides to intercept events, the sequence of events will be handled by the View.

So you also noticed the flag FLAG_DISALLOW_INTERCEPT, which seems to affect whether ViewGroup intercepts the event. This flag is set by the request Disallow InterceptTouchEvent () method, which is commonly used in sub-Views. When the flag is set, ViewGroup will not be able to intercept events other than ACTION_DOWN. Why except ACTION_DOWN? Because dispatchTouchEvent initializes the state every time it receives ACTION_DOWN, the code is as follows.

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
   cancelAndClearTouchTargets(ev);
   resetTouchState();
}

In summary, the request Disallow InterceptTouchEvent () method does not affect ACTION_DOWN events.
To sum up, the onInterceptTouchEvent() method may not always be executed every time. If you want to handle every event, it's in dispatchTouchEvent().

Let's move on. When the ViewGroup does not intercept the click event, the event will be passed to its child View:

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
    final float x = ev.getX(actionIndex);
    final float y = ev.getY(actionIndex);
    
    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
    final View[] children = mChildren;
    for (int i = childrenCount - 1; i >= 0; i--) {
        final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(
                preorderedList, children, childIndex);

        if (childWithAccessibilityFocus != null) {
            if (childWithAccessibilityFocus != child) {
                continue;
            }
            childWithAccessibilityFocus = null;
            i = childrenCount - 1;
        }

        if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
            ev.setTargetAccessibilityFocus(false);
            continue;
        }

        newTouchTarget = getTouchTarget(child);
        if (newTouchTarget != null) {
            newTouchTarget.pointerIdBits |= idBitsToAssign;
            break;
        }

        resetCancelNextUpFlag(child);
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            mLastTouchDownTime = ev.getDownTime();
            if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                    if (children[childIndex] == mChildren[j]) {
                        mLastTouchDownIndex = j;
                        break;
                    }
                }
            } else {
                mLastTouchDownIndex = childIndex;
            }
            mLastTouchDownX = ev.getX();
            mLastTouchDownY = ev.getY();
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }
        ev.setTargetAccessibilityFocus(false);
    }
    if (preorderedList != null) preorderedList.clear();
}

The above section is the main code of ViewGroup for event distribution, which seems relatively simple. When the ViewGroup has a sub-View, it traverses the sub-View with one criterion:

canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null) 

Determine whether the current click event is within the coordinate range of the sub-View, and the sub-View is not moving in the coordinate system (executing animation). If the sub-View meets the above two conditions, then pass the click event to him for processing. Go down and you will see such a judgment condition:

dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

This method is actually used to distribute events to sub-View s. Look at one of the source codes of this method and you will get a lot clearer.

final int oldAction = event.getAction();
if (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;
}

If the child View is null, then give the dispatch TouchEvent () of the ViewGroup, and vice versa, give the click event to the child View (or possibly the ViewGroup) for processing, and a distribution is completed.

If dispatchTransformedTouchEvent() returns true, indicating that the click event is consumed by the child View, the addTouchTarget() method is executed to assign the initial mFirstTouchTarget.

If you have traversed all the sub-Views, click events are not consumed, there may be two situations: First, there is no sub-View under the ViewGroup. Second, sub-View does not consume click events. In both cases, the ViewGroup handles the click event itself. When the child View does not consume click events, the click events are handed over to his parent View.

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

In the code, the child parameter is assigned to null. When the child is null, access the super.dispatchTouchEvent(event) of the current ViewGroup, because the ViewGroup is inherited from View, so the dispatchTouchEvent() method of View is actually accessed.

3.View Distribution of Events
Look at the dispatchTouchEvent() method of View in one of the code, note that one of the knowledge, space can not be too long, want to view all must open Studio to see the source code!

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;
}

View's dispatch TouchEvent () is simpler. onFilterTouchEvent ForSecurity (event) is used to determine whether the window is blocked when the click event arrives. If blocked, it returns false directly without consuming the event.
Conversely, when you receive an event and see a class called ListenerInfo, what is this? Look at the source code!

static class ListenerInfo {
    public OnClickListener mOnClickListener;

    protected OnLongClickListener mOnLongClickListener;

    private OnKeyListener mOnKeyListener;

    private OnTouchListener mOnTouchListener;
    
    ......
}

After looking at the source code, it is found that it is a static internal class of View, which defines a series of Liisteners.
Continuing to look at the source code of View's dispatchTouchEvent(), it is found that View will first determine whether it has set OnTouchListener. If the OnTouchListener set returns true, the click event will be consumed directly and the onTouch Event () method will not be executed.

It is concluded that OnTouchListener has higher priority than onTouchEvent(). The advantage of this is that it is easy to handle events externally.

If you don't set up OnTouchListener, you will execute onTouchEvent() of View. Continue to look at the source code of onTouchEvent(). Let's go on for a while. It's a bit long.

if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
    return (((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}

When we set View to be unavailable, View still consumes click events, but looks unavailable.

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
        return true;
    }
}

Then, if the View has a proxy set up, the onTouchEvent() of the proxy is executed directly. Next, let's look at the main code for click events:

if (((viewFlags & CLICKABLE) == CLICKABLE ||
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
        (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    switch (action) {
        case MotionEvent.ACTION_UP:
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }

                if (prepressed) {
                    setPressed(true, x, y);
               }

                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    removeLongPressCallback();
                    if (!focusTaken) {
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }

                if (mUnsetPressedState == null) {
                    mUnsetPressedState = new UnsetPressedState();
                }

                if (prepressed) {
                    postDelayed(mUnsetPressedState,
                            ViewConfiguration.getPressedStateDuration());
                } else if (!post(mUnsetPressedState)) {
                    mUnsetPressedState.run();
                }

                removeTapCallback();
            }
            mIgnoreNextUpEvent = false;
            break;

        case MotionEvent.ACTION_DOWN:
            ......
            break;

        case MotionEvent.ACTION_CANCEL:
            ......
            break;

        case MotionEvent.ACTION_MOVE:
            ......
            break;
    }

    return true;
}

View consumes this event when one of the CLICKABLE, LONG_CLICKABLE and CONTEXT_CLICKABLE of View is true. And the performClick() method is executed at ACTION_UP:

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

If View sets OnClickListener, the method performClick() executes the listening event.
Another conclusion is that OnTouchListener has higher priority than OnClickListener, which is executed at ACTION_UP.

See here the source code analysis of event transmission mechanism is finally over!!!


conclusion

  • Event distribution mechanism is the distribution of click events. The same sequence of events generated after the finger touches the screen is click events.
  • The order of click events is from father to son, and then from son to father.
  • Normally events can only be intercepted by one View.
  • If View decides to intercept events, the sequence of events will be handled by the View.
  • When a child View does not consume click events, the click events are handed over to his parent View. If all views do not consume click events, Activity calls its own onTouchEvent.
  • The onInterceptTouchEvent() method does not necessarily execute every time. If you want to handle every event, it's in dispatchTouchEvent().
  • OnTouchListener has higher priority than onTouchEvent(). The advantage of this is that it is easy to handle events externally.
  • When we set View to be unavailable, View still consumes click events, but looks unavailable.

Finally, I recommend an article about View source code analysis, which contains Log log analysis. Let's take a look to deepen our understanding. Android View Event Distribution Mechanism Source Details (View)

Finally, I would like to recommend a Book "Exploration of Android Development Art", which is sold on all major websites. It is of great significance to break through the bottleneck.

After conclusion

It took the blogger some time to finally straighten out this article. Of course, due to the blogger's technical reasons, the article is not perfect, only hope to give friends who are still in a confused period a direction.
I hope that my article can bring you a little benefit, that is enough to be happy next.
See you next time!

Topics: Android