preface

When it comes to event distribution, many people will think of the View dispatchTouchEvent. In fact, Android has done a lot of work before that.

How do you get input events across processes? There’s an InputStage responsibility chain before the dispatchTouchEvent responsibility chain, right? DecorView, PhoneWindow pass order?

What is the processing of the sequence of events during event distribution? Coordination between ViewGroup and View? MFirstTouchTarget true or false list? And so on.

It all starts with your cute little finger…

The moment your thumb touches your phone, it will be deeply affected by you. Yes, it will receive the task you assign to it.

This task could be:

  • Sliding interface task
  • Click button task
  • Long according to the task

Wait. Anyway, you transmit the task to the phone, and then it’s time for the phone to process the task.

We can assume that the mobile phone system is a large company (Android Company), and our task of touching the mobile phone is a complete project requirement. Today, we will go deep into the Android company and explore the secrets of event distribution.

Before that, I have also listed the questions and Outlines:

Hardware and kernel

First, my thumb went to the Android company and said what it wanted, such as clicking on a View and swiping to another location.

The Android company would send the hardware department to talk with my little finger. After receiving my requirements, the hardware department would generate a simple terminal and pass it to the kernel department.

The kernel department processed the task and generated an internal event — event, which was added to the /dev/in/ directory of an internal management system in the company.

The goal is to turn external requirements into internally common, universally understood tasks.

Task handling department (SystemServer process)

When tasks are logged on the corporate management system, there is a task processing department that handles them. What they do is always listen for new events in the /dev/inpu/directory.

So who exactly is this task force?

I don’t know if you remember starting a series of system-specific services in the SystemServer process, such as AMS, PMS, etc. There is also an obscure role called InputManagerService.

This service is responsible for communicating with the hardware and receiving on-screen input events.

Internally, a reader thread, called InputReader, is started, which takes tasks from the management system, the /dev/inpu/ directory, and dispatches them to the InputDispatcher thread for unified event dispatch.

Assigned to a specific project group (InputChannel)

The task handling department then needs to hand off the task to the project team that specializes in handling the task, which involves cross-departmental communication (cross-process communication).

As we all know, cross-departmental communication is a troublesome matter. Who will complete this matter? InputChannel.

Let’s go back to the setView method of ViewRootImpl:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
      / / create InputChannel
      mInputChannel = new InputChannel();
      // Use Binder to enter the systemServer processres = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); }}Copy the code

In this method, an InputChannel object is created and entered by Binder into the SystemServer process to form the client of the socket.

Here involves the knowledge of socket communication, the more important is the C layer socketpair method.

The socketpair() function is used to create a pair of unnamed, interconnected sockets. If the function succeeds, 0 is returned and the created sockets are SV [0] and SV [1], respectively. This pair of sockets can be used for full-duplex communication, and each socket can be read or written.

With this method, the client and server for socket communication are generated:

  • The socket serverWindowState mInputChannel saved to system_server;
  • The socket clientMInputChannel of viewrotimPL, the MAIN UI thread returned to the remote process via binder;

For those interested, check out Gityuan’s blog on input analysis, which is linked at the end.

Binder creates an InputChannel object in the App process, which is passed into the SystemServer process, Windows ManagerService. A socket pair is then created in WindowManagerService for interprocess communication, and the InputChannel passed through points to the socket client.

Then App process’s main thread will monitor this socket client, when after receiving the news events (output), the callback NativeInputEventReceiver. HandleEvent () method, Will eventually go InputEventReceiver dispachInputEvent method.

DispachInputEvent, to handle the input event, feels closer to the event distribution that we are familiar with.

Yes, at this point, the task has been assigned to the specific project team, that is, the specific APP that we are using.

First assignment in group (InputStage)

When a task arrives at the project team, the task will be first distributed within the group, which involves the first distribution mode of responsibility chain.

Why emphasize the first time? Since we have not yet reached the familiar View event distribution phase, there will be another chain of responsibility distribution for event classification, known as the InputStage, which handles event distribution.

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

//ViewRootImpl.java ::WindowInputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
       enqueueInputEvent(event, this.0.true); }}//ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;

    if (processImmediately) {
        doProcessInputEvents(); 
    } else{ scheduleProcessInputEvents(); }}Copy the code

We ended up in ViewRootImpl, so ViewRootImpl is responsible not only for drawing the interface, but also for some of the handling of event distribution.

The enqueueInputEvent method involves a QueuedInputEvent class, which is an event class that wraps InputEvent and is then assigned to the doProcessInputEvents method:

   void doProcessInputEvents(a) {
        // Deliver all pending input events in the queue.
        while(mPendingInputEventHead ! =null) { QueuedInputEvent q = mPendingInputEventHead; mPendingInputEventHead = q.mNext; deliverInputEvent(q); }}private void deliverInputEvent(QueuedInputEvent q) {
        InputStage stage;
        if(stage ! =null) {
            stage.deliver(q);
        } else{ finishInputEvent(q); }}abstract class InputStage {
        private final InputStage mNext;

        public InputStage(InputStage next) {
            mNext = next;
        }

        public final void deliver(QueuedInputEvent q) {
            apply(q, onProcess(q));
        }
Copy the code

QueuedInputEvent is an input event, InputStage is the responsibility chain that handles the input event, and the next field represents the next InputStage in the responsibility chain.

So what does InputStage do? Go back to the setView method of ViewRootImpl and look again:

	public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
		// Set up the input pipeline.
        mSyntheticInputStage = new SyntheticInputStage();
        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
        InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                 "aq:native-post-ime:" + counterSuffix);
        InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
        InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                "aq:ime:" + counterSuffix);
        InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
        InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:"+ counterSuffix); mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage; }}Copy the code

As you can see, in the setView method, the chain of responsibility for handling the input event has been concatenated. The different subclasses of InputStages have been concatenated by the constructor. So what do these InputStages do?

  • SyntheticInputStage. Integrated event processing phases, such as handling navigation panels, joysticks, and so on.
  • ViewPostImeInputStage. The view input processing phase, such as keystrokes, finger touches, and other motion events, is known as view event distribution.
  • NativePostImeInputStage. Local method processing phase, mainly build deferred queue.
  • EarlyPostImeInputStage. Input method early processing stage.
  • ImeInputStage. Input method event processing phase, processing input method characters.
  • ViewPreImeInputStage. In the view preprocessing input method event phase, the dispatchKeyEventPreIme method of the view is called.
  • NativePreImeInputStage. The local method preprocesses the input method event stage.

To recap, the main thread to the application handles the event through a series of Inputstages via ViewRootImpl. This stage is actually a simple classification of events, such as view input events, input method events, navigation panel events, and so on.

After the event is distributed, the InputDispatcher thread of the SystemServer process is notified and removed to complete the distribution and consumption of the event.

Our view finger touch event takes place in the ViewPostImeInputStage.

    final class ViewPostImeInputStage extends InputStage {
        @Override
        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) {
                    returnprocessPointerEvent(q); }}}private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            boolean handled = mView.dispatchPointerEvent(event)
            return handled ? FINISH_HANDLED : FORWARD;
        }

//View.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
            if (event.isTouchEvent()) {
                return dispatchTouchEvent(event);
            } else {
                returndispatchGenericMotionEvent(event); }}Copy the code

After a series of dispatches, we end up executing the dispatchTouchEvent method to the mView, which is a DecorView that is also assigned in the setView, so I won’t go into details.

At this point, we finally get to the familiar part, the dispatchTouchEvent method.

Task collation between bosses (DecorView)

DecorView, PhoneWindow, and Activity/Dialog:

//DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //cb is the corresponding Activity/Dialog
        final Window.Callback cb = mWindow.getCallback();
        returncb ! =null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }


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

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

//DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }    
Copy the code

As you can see, from the DecorView, the event passes through the Activity, PhoneWindow, and DecorView.

It’s a little weird. Why is it in this order? Instead of passing the viewrotimpl directly to the Activity and then to the top-level View — DecorView? But turn around, cause and from?

  • First, why doesn’t ViewRootImpl just hand events to the Activity?

If there is a Dialog on the interface, and the Dialog Window is a sub-window, it can override the application-level Window, so you can’t give the event directly to the Activity. Are overwritten, so this is the time to hand the event to the Dialog.

For convenience, we use the DecorView role as the first element to distribute to find the holding of the current interface Window, so the code also finds mWindow.getCallback(), which is the corresponding Activity or Dialog.

  • Second, once you hand it to Acitivity, why not just hand it to the top-level View — the DecorView starts distributing events?

There is no direct relationship between an Activity and a DecorView. Where does a DecorView come from? The setContentView is created, so there is no DecorView in the Activity. Instances of the DecorView are stored in the PhoneWindow, which is managed by the Window.

PhoneWindow’s job is to help the Activity manage the View, so it’s also its job to distribute events to it. The PhoneWindow is handled by the top-level DecorView.

Thus, a chain of event distribution is formed:

DecorView – > Activity – > PhoneWindow – > DecorView – > ViewGroup

Give it to the task specific person (ViewGroup)

And then it’s time to dispatch, which is the event dispatch time for the ViewGroup, which is a cliche, but the most important thing is this dispatchTouchEvent method.

Assuming we haven’t looked at the source code, then the event comes and there are many possibilities for passing interception. I drew a brain diagram:

Among the questions raised are:

  • ViewGroupWhether to intercept events and how to handle them?
  • Do not intercept after deliveryThe child VieworThe child ViewGroupHow to deal with it?
  • The child ViewHow do you decide whether to intercept?
  • The child ViewHow to handle events after interception?
  • The child ViewDo not intercept events afterParent ViewGroupHow to handle events?
  • ViewGroupDon’t intercept,The child ViewWhat happens to the final event?

Let’s do a detailed analysis.

Does the ViewGroup intercept events and how to handle them?

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        / / 1
        final boolean intercepted;
        if(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget ! =null) {
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } 
        } 

        / / 2
        if(! canceled && ! intercepted) {// The event is passed to the child view
        }

        / / 3
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }}private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else{ handled = child.dispatchTouchEvent(event); }}Copy the code

The above code is divided into three parts, including whether the ViewGroup intercepts, no longer passes after intercepting, and processing after the ViewGroup intercepts.

1. Whether the ViewGroup is blocked

As you can see, a variable intercepted is initialized to indicate whether the viewGroup is intercepted.

ViewGroup interception is discussed only if either of the two conditions are met:

  • Events forACTION_DOWNThat is, press the event.
  • mFirstTouchTargetNot null

MFirstTouchTarget is a linked list structure, which means that a child element successfully consumed the event, so mFirstTouchTarget null means that there is no child View consumption event, but I’ll talk more about that later. When you first enter this method, the event must be ACTION_DOWN, so you go into the if statement, and you get a variable called disallowIntercept, so click on the disallowIntercept for now, and watch. We then give the intercepted value as the result of the onInterceptTouchEvent method, which can be interpreted as whether the viewGroup intercepts depends on the onInterceptTouchEvent method.

2, interception is no longer transmitted

If the viewGroup intercepts (intercepted is true), there is no need to pass to the child view or child viewGroup.

3. Processing after ViewGroup interception

If mFirstTouchTarget is null, the said no children View to intercept, then turned to perform dispatchTransformedTouchEvent method, on behalf of ViewGroup to distribute processing again.

The question here is why not just say intercepted? Do you have to judge this mFirstTouchTarget?

  • becausemFirstTouchTarget==nullNot only does it mean that the ViewGroup has to consume the event itself, it also means thatViewGroupNo consumption andThe child ViewThere’s no consumption event, so both cases are executed here.

Namely ViewGroup intercept or child View without intercept, will call to dispatchTransformedTouchEvent method, in this method, finally will call super. DispatchTouchEvent.

Super represents the parent View of the ViewGroup, so the ViewGroup will execute the view. dispatchTouchEvent method as a normal View, but what that method does, we’ll see later with the View’s event handling.

Through the above analysis, we can get the pseudo code intercepted by ViewGroup:


public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        if (onInterceptTouchEvent(event)) {
            isConsume = super.dispatchTouchEvent(event); }}return isConsume;
}
Copy the code

If it is a ViewGroup, the onInterceptTouchEvent method is used to check whether it is intercepted. If it is, the dispatchTouchEvent method of the parent View class is executed.

ViewGroup does not intercept and then give it to a child View or child ViewGroup?

The ViewGroup does not intercept cases that are passed to child views:

    if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int childrenCount = mChildrenCount;

            / / 1
            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);

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

                    / / 3
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
        }
    }
Copy the code

If the ViewGroup does not intercept, intercepted is false and goes into the if statement above.

It is also divided into three parts, namely, traversing the sub-view, judging the coordinate of the event and passing the event

1. Iterate over subViews

The first part is going to walk through all the child views of the current ViewGroup.

2. Determine the event coordinates

Then it will determine whether the event is in the coordinates of the current child View. If the user touches the place that is not the current View, there is no need to distribute the View. There is also a condition that the current View is not in the animation state.

3. Pass events

If the event coordinates in this View, begin to pass events, call dispatchTransformedTouchEvent method, if it is true, just call addTouchTarget method record events consumption chain.

A little familiar with dispatchTransformedTouchEvent method? That’s right, it just happened. Watch it again:

    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else{ handled = child.dispatchTouchEvent(event); }}Copy the code

If the child View is not null, the dispatchTouchEvent method of the child View is called and the event continues to be distributed. If null, as was the case before, the parent’s dispatchTouchEvent method is called, which by default consumes the event itself.

And, of course, the child could be a viewGroup it could be a View, but it just keeps distributing methods that call child views or child viewgroups.

At this point, a recursion of dispatchTouchEvent appears: If a ViewGroup is unable to consume the event, then the dispatchTouchEvent method is passed to the child view/ child ViewGroup, and if it is a ViewGroup, the process is repeated until a view/ ViewGroup consumes the event.

Finally, if dispatchTransformedTouchEvent method returns true, means the son view consumer events, and then will call to addTouchTarget method:

In this method, the single linked list mFirstTouchTarget is assigned, the consumption chain is recorded (but in the single touch case, the single linked list structure is not used, just as a normal TouchTarget object, more on that later), and the loop is broken.

Now let’s take a look at the logic of how events are handled inside a View.

How does a subview handle events, whether to intercept?

public boolean dispatchTouchEvent(MotionEvent event) {
        
        if (onFilterTouchEventForSecurity(event)) {
            
            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

It’s really two pieces of logic:

  • 1. If the View is setsetOnTouchListenerandonTouchMethod returns true, thenonTouchEventIt will not be executed.
  • 2. If no, run the commandonTouchEventMethods.

So by default, the onTouchEvent method is executed directly.

We can also write pseudocode for View event distribution and add a call to the setOnClickListener method:

public void consumeEvent(MotionEvent event) {
    if(! setOnTouchListener || ! onTouch) { onTouchEvent(event); }if(setOnClickListener) { onClick(); }}Copy the code

How to handle the event after the child View intercepts?

Once the child View intercepts, it assigns the single linked list mFirstTouchTarget.

I already talked about that. The logic is in the addTouchTarget method, so let’s see:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        final TouchTarget target;
        target.child = child;
        return target;
    }
Copy the code

How is this single linked list connected? We said dispatchTouchEvent is a recursive process, and when a child View consumes the event, then addTouchTarget will point the mFirstTouchTarget child to that child View, and up, It ends up being spliced together into something like a single linked list, with the last node being the View to consume.

Why is it similar? Because the MFirstTouchTargets are not really concatenated, they are concatenated between the MFirstTouchTargets of each ViewGroup.

For example, let’s assume a View tree relationship:

    A
   / \
  B   C
    /  \
   D    E
Copy the code

A, B, and C are viewgroups, D, and E are views.

When the point we touch is in ViewD, the order of events is a-C-D.

When C traverses D, ViewD consumes the event, so it goes to the addTouchTarget method, which wraps a TouchTarget containing ViewD, which we call TargetD.

Then set C’s mFirstTouchTarget to TargetD, its child value to ViewD.

C’s dispatchTouchEvent method also returns true because D consumes the event, and addTouchTarget is also called, wrapping A TargetC.

It then sets A’s mFirstTouchTarget to TargetC, which is its child value to ViewC.

The final distribution structure is:

A.mFirstTouchTarget.child -> C

C.mFirstTouchTarget.child -> D

So mFirstTouchTarget finds the View at the next level of the consumption chain through the Child, and then the next level finds the View at the next level through the Child, and so on, recording the entire path of the consumption.

So where does mFirstTouchTarget use the list structure? Multi-touch.

For multi-touch and different targets, the mFirstTouchTarget exists as a linked list structure, and next points to the TouchTarget object created when the last finger was pressed.

In the single touch case, the mFirstTouchTarget list morphs into a single TouchTarget object:

  • mFirstTouchTarget.nextAlways null.
  • mFirstTouchTarget.childAssign to the next level of View in the consumption chain, and recursively call mFirstTouchTarget.Child on each level until the View is consumed.

Finally, every time an ACTION_DOWN event arrives, the mFirstTouchTarget is reset to welcome a new round of events.

How does the ViewGroup handle the event if the child View does not intercept the event?

Son View does not intercept events, then mFirstTouchTarget null, retired from circulation, call the dispatchTransformedTouchEvent method.

        / / 3
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        }
Copy the code

The result is a call to super.dispatchTouchEvent, which is the view.dispatchTouchEvent method.

You can see that subviews that don’t block events are handled the same way ViewGroup blocks events and they’re going to go into this method.

So what does this method actually do? I already talked about the View method dispatchTouchEvent, which is the same pseudocode, but in this case the View is the parent of the ViewGroup.

So, to summarize, if all child Views do not handle events, then:

  • The default implementationViewGrouptheonTouchEventMethods.
  • If setViewGroupthesetOnTouchListener, will be executedonTouchMethods.

ViewGroup doesn’t block, subviews don’t block, so what happens to the final event?

Finally, if you don’t intercept ViewGroup, View also not intercept, this means mFirstTouchTarget = = null, dispatchTransformedTouchEvent method also returns false.

All ViewGroup dispatchTouchEvent methods return false. Go back to the beginning of the big talk:

//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
Copy the code

That’s right, if the superDispatchTouchEvent method returns false, the Activity’s onTouchEvent method is executed.

summary

To summarize:

  • The essence of event distribution is a recursive method that finds the handler of the event by passing it down and calling the dispatchTouchEvent method, which is the chain of responsibility pattern common in projects.

  • During consumption, the ViewGroup is handled by onInterceptTouchEvent

  • During consumption, the View is handled by the onTouchEvent method.

  • If the underlying View does not consume, the onTouchEvent method of the parent element is executed step by step.

  • If all views’ onTouchEvent methods return false, the Activity’s onTouchEvent method is finally executed, and event distribution ends.

Complete event consumption pseudocode:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        //ViewGroup
        if (onInterceptTouchEvent(event)) {
            isConsume = consumeEvent(event);
        } else{ isConsume = child.dispatchTouchEvent(event); }}else {
        //View
        isConsume = consumeEvent(event);
    }

    if(! isConsume) {// If the child View does not consume, call the consume method
        isConsume = consumeEvent(event);
    }
    return isConsume;
}


public void consumeEvent(MotionEvent event) {
    // The logic that consumes the event itself is called onTouchEvent by default
    if(! setOnTouchListener || ! onTouch) { onTouchEvent(event); }}Copy the code

DispatchTouchEvent () + onInterceptTouchEvent() + onTouchEvent(). You can also use these three methods to understand the distribution of memory events.

Follow-up task processing (Sequence of events)

Finally, the task has found its master, and it seems that the process is over, but there is still a question: how to deal with the follow-up tasks after the task? For example, the function of certain module should be added.

You can’t go through company procedures again, can you? The logical thing to do would be to find the person who was in charge of our task and work on it, and see if that’s what Android is doing.

A sequence of motionEvents typically consists of:

ACTION_DOWN, ACTION_MOVE, ACTION_UP, and ACTION_CANCEL

So we’ve been talking about ACTION_DOWN, which is the event that the phone presses down, but what about the subsequent event that the phone moves away from the screen?

Suppose there was already an ACTION_DOWN and it was consumed by a child View, so the mFirstTouchTarget will have a full pointer to it, and then the second event, ACTION_MOVE, comes in.

    if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {          
    }

Copy the code

Then you’ll notice that the ACTION_MOVE event does not enter the child View’s looping method at all, but goes straight to the logic at the end:

    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget target = mFirstTouchTarget;
        while(target ! =null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                       handled = true; } } predecessor = target; target = next; }}Copy the code

If mFirstTouchTarget is null, that’s the onTouchEvent method that goes to the ViewGroup itself.

This is obviously not null, so I’m going to go to the else, and I’m going to iterate over the mFirstTouchTarget again. When I said single touch, target.next is null, target.child is the View at the next level of the consumer chain, so I’m actually passing the event to the View at the next level.

There is one point before a lot of friends may not notice, is when ACTION_DOWN, go here, will find the consumption View through mFirstTouchTarget dispatchTransformedTouchEvent execution. But before this, through the View of time has imposed a dispatchTransformedTouchEvent method, is here to perform a dispatchTransformedTouchEvent method? Isn’t that a repeat?

  • And that brings us to another variablealreadyDispatchedToNewTouchTarget. This variable represents whether a View consumption event has been executed before, when the event isACTION_DOWNIf the View consumes the event, thenalreadyDispatchedToNewTouchTargetIt’s set to true, so it won’t be executed again at this point, straight awayhandled = true.

So the processing logic of the follow-up tasks is basically clear:

Once a View starts processing intercepting events, the entire sequence of events is left to it.

Optimize the task dispatch process (solve sliding conflicts)

At this point, the assignment was finally distributed. After the assignment was completed, the team held a summary meeting:

In fact, there are some processes that can be optimized in the task distribution process. For example, some tasks may not be assigned to only one person, for example, two people should be assigned to do the tasks that A is good at and B is good at, so as to make the best use of everyone.

However, the default of our previous logic was to press the task and give it to A, and then give it to A. This is where you need to design a mechanism to intercept certain tasks.

In fact, this involves the sliding conflict problem, for example, a scenario:

The outer ViewGroup moves horizontally, while the inner ViewGroup moves vertically, so you need to judge and intercept events in ACTION_MOVE. (ViewGroup+Fragment+Recyclerview)

To get straight to Android’s solutions, there are two:

  • External interception.
  • Internal interception.

External interception

The external intercepting method is simpler because the onInterceptTouchEvnet method is executed every time, regardless of whether the View intercepts or not. In this method, we can choose whether or not to intercept events based on our business conditions.

    // External interception: parent view.java
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        // The parent view intercepts the condition
        boolean parentCanIntercept;

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        return intercepted;

    }
Copy the code

The logic is very simple, it is based on the business conditions in the onInterceptTouchEvent to decide whether to intercept, because this method is in the parent View control whether to intercept, so this method is called external interception method.

If ACTION_DOWN is handled by the child View, subsequent events should be sent directly to the child View. Why is it blocked by the parent View?

Let’s look at the dispatchTouchEvent method again:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final boolean intercepted;
        if(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget ! =null) {
            intercepted = onInterceptTouchEvent(ev);
        } 

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        } else {
            while(target ! =null) {
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                }
            }
        }
    }
Copy the code

When the event is ACTION_MOVE, and the onInterceptTouchEvent method returns true, intercepted=true, cancelChild =true, Is then passed to the dispatchTransformedTouchEvent method, yes, and this method, different is a cancelChild son is true.

The name of this field must be associated with canceling the child view event.

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        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);
            returnhandled; }}Copy the code

See, when the second field cancel is true, the event is changed to ACTION_CANCEL!! And then it will be passed on.

So even if a View consumes ACTION_DOWN, but returns true in the parent’s onInterceptTouchEvent(), the event will be modified to an ACTION_CACLE event and passed to the child View.

So the subview again surrenders control of the sequence of events, which is why external interception works.

Internal interception method

Moving on to internal interception:

    / / the parent view. Java
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true; }}/ / child view. Java
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // The parent view intercepts the condition
        boolean parentCanIntercept;

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

Copy the code

Internal interception is to give the initiative to the child View. If the child View needs an event, it consumes it; otherwise, the parent container handles it. Let’s enumerate two cases: DOWN and MOVE.

  • ACTION_DOWNThe child View must be able to consume, so the parent View’sonInterceptTouchEventReturn false, otherwise the parent View will intercept and subsequent events will not be passed to the child View.
  • ACTION_MOVEWhen the parent ViewonInterceptTouchEventMethod returns true, indicating that the parent View can consume when the child View does not want to consume. You can see that the code sets onerequestDisallowInterceptTouchEventMethod, what is this?
    protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else{ mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; }}Copy the code

Through this | = and & = ~ operator modify parameter is the source of common set identification methods:

  • | =Set the flag bit to 1
  • & = ~Set the identity bit to 0

So the need to set up the parent to intercept requestDisallowInterceptTouchEvent (false) method, let the sign bit is set to 0, so the parent element can perform to onInterceptTouchEvent method.

The code is in the dispatchTouchEvent method:

    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    final boolean intercepted;
    if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
        final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
        if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action);// restore action in case it was changed
        } else {
            intercepted = false; }}Copy the code

As you can see, if disallowIntercept is false, it means that the parent View is intercepting. OnInterceptTouchEvent is executed.

conclusion

After the visit of thumb reporter, finally put the Android company for the event task processing clear, hope for the screen before you can have some help, see you next time.

reference

“Android development exploration of art” daily asking | event is first to DecorView or to the Window? Input system – event handling process Reflection | Android events distribution mechanism, the design and implementation of the View, InputEvent event delivery source code analysis Distribution of temporal thoroughly grasp the Android touch events

Bye bye

Thank you for your reading. If you study with me, you can pay attention to my public account — building blocks on the code ❤️❤️, a knowledge point every day to establish a complete knowledge system architecture. Here is a group of Android friends, welcome to join us