Thumb reporter went deep into Android company to find out the secrets behind the event distribution mechanism

Time:2021-12-3

preface

When talking about event distribution, many friends will think of viewdispatchTouchEventIn fact, Android has done a lot of work before that.

For example, how to get input events across processes? staydispatchTouchEventThere is another one before the chain of responsibilityInputStageChain of responsibility?DecorView,PhoneWindowTransfer order between?

In addition, it also includes the processing method of event sequence during event distribution? Coordination between ViewGroup and view?mFirstTouchTargetTrue and false linked list? wait.

All this, from your lovelylittle fingerSpeaking of

When your thumb touches the mobile phone, the mobile phone will be deeply influenced by you. Yes, the mobile phone will receive the task you assigned him.

This task can be:

  • Sliding interface task
  • Click button task
  • Long press task

Wait, in a word, you sent the task information to the mobile phone, and then the task processing time of the mobile phone.

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

Before that, I also listed the questions and outline:

2.png

Hardware department and kernel Department

First, my thumb found itAndroidFor example, click a view and slide to another location.

AndroidThe company will send the hardware department to talk with my thumb. After receiving my requirements, the hardware department will generate a simple terminal and pass it to the kernel department.

The kernel department processes the task, generates an internal event – event, and adds it to a management system within the company /dev/input/Directory.

The purpose of this is to transform external requirements into internal common tasks that can be understood.

Task processing Department (systemserver process)

When tasks are recorded in the company’s management system, there will be a special task processing department to process these tasks. What they do is to listen all the time/dev/input/Directory. When a new event is found, it will be processed.

So where is this task processing department?

I don’t know if you rememberSystemServerIn the process, a series of system related services are started, such as AMS, PMS, etc. there is also a less prominent role calledInputManagerService

This service is used to communicate with hardware and accept screen input events.

Internally, a read thread is started, that isInputReader, it will start from this management system, that is/dev/input/The catalog gets the task and distributes itInputDispatcherThread, and then conduct unified event distribution scheduling.

Assigned to a specific project group (inputchannel)

Then the task processing department needs to hand over the task to the professional task processing project team, which involves cross departmental communication (cross process communication).

As we all know, cross departmental communication is a troublesome thing. Who will complete it?InputChannel

Let’s go back toViewRootImplofsetViewmethod:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
      //Create inputchannel
      mInputChannel = new InputChannel();
      //Enter the systemserver process through binder
      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                  getHostVisibility(), mDisplay.getDisplayId(),
                  mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                  mAttachInfo.mOutsets, mInputChannel);
    }
}

In this method, aInputChannelObject, and enter the systemserver process through binder, and finally form the socket client.

This involves the knowledge of socket communication, and the more important is the C layersocketpairmethod.

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

Through this method, the client and server of socket communication are generated:

  • Socket serverSave to system_ Mainputchannel of windowstate in server;
  • Socket clientThrough the binder, it is transmitted back to the MainUI thread of the remote process, the maininputchannel of viewrootimpl;

Interested can have a lookgityuanFor the blog of input analysis, there is a link at the end of the article.

So to summarize, an object is created in the app processInputChannel, passed in through binder mechanismSystemServerProcess, that isWindowManagerServiceYes. Then inWindowManagerServiceA pair of sockets are created for interprocess communicationInputChannelIt points tosocketClient for.

Then the main thread of the app process will listen to the socket client and call back after receiving a message (output event)NativeInputEventReceiver.handleEvent()The method will eventually come toInputEventReceiver.dispachInputEventmethod.

dispachInputEvent, processing input events feels close to the well-known event distribution.

Yes, so far, the task has been assigned to the specific project team, that is, the specific app we use.

First distribution of tasks in the group (inputstage)

When a task arrives at the project team, it will be distributed within the team first, which will involve the first timeResponsibility chain distribution mode

Why is it the first time? Because we have not reached the well-known view event distribution stage, before that, there will be a responsibility chain distribution of event classification, that isInputStageHandle 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();
    }
}

Walking around, I didn’t expect to get to viewrootimpl, soViewRootImplNot only responsible for the drawing of the interface, but also responsible for some processing of event distribution.

thereenqueueInputEventIn the method, one is involvedQueuedInputEventClass, which is an event class encapsulating inputevent, and then called todoProcessInputEventsmethod:

void doProcessInputEvents() {
        // 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));
        }

The logic seems to be getting clearer here,QueuedInputEventIs an input event,InputStageIs the responsibility chain for handling input events, and the next field represents the next in the responsibility chainInputStage

thatInputStageWhat did you do? Return toViewRootImplTake a look at the setview method of:

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

You can see that in the setview method, the responsibility chain of input event processing is spliced. Different inputstage subclasses are connected one by one through the construction method. What are these inputstages doing?

  • SyntheticInputStage。 Comprehensive event processing stage, such as handling navigation panel, joystick and other events.
  • ViewPostImeInputStage。 View input processing stage, such as key press, finger touch and other motion events. The well-known view event distribution occurs in this stage.
  • NativePostImeInputStage。 In the local method processing phase, the delay queue is mainly constructed.
  • EarlyPostImeInputStage。 Early processing stage of input method.
  • ImeInputStage。 The input method event processing stage processes input method characters.
  • ViewPreImeInputStage。 In the view preprocessing input method event stage, call the dispatchkeyeventpreime method of view.
  • NativePreImeInputStage。 The local method preprocesses the input method event phase.

To summarize, when an event reaches the main thread of the application side, it will process a series of inputstage events through viewrootimpl. This stage is actually a simple classification of events, such as view input events, input method events, navigation panel events, etc.

After the event distribution is completed, it will be notifiedSystemServerProcessInputDispatcherThread, and finally remove the event to complete the distribution and consumption of this event.

Our finger touch event happened inViewPostImeInputStageStage. Let’s take a look at the details:

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) {
                    return processPointerEvent(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 {
                return dispatchGenericMotionEvent(event);
        }
    }

After a series of distributions, it will eventually be executed to MviewdispatchTouchEventMethod, and this Mview is decorview, which is also assigned in setview, so I won’t elaborate.

At this point, we finally reached the familiar link,dispatchTouchEventmethod.

Decorview between big guys

After determining the classification of tasks, we will start to discuss and sort out the tasks in the group. This stage occurs in the conversation between several leaders, who areDecorView、PhoneWindow、Activity/Dialog

//DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //CB is actually the corresponding activity / dialog
        final Window.Callback cb = mWindow.getCallback();
        return cb != 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);
    }

You can see that from decorview, events pass in turnActivity、PhoneWindow、DecorView

It’s a little strange. Why is this order? Instead of directly giving viewrootimpl to activity and then to the top-level view – decorview? But turn around, origin and origin?

  • First, why doesn’t viewrootimpl give events directly to the activity?

Because there are more thanActivityA form, if it exists on the interfaceDialogThe window of dialog belongs to a child window and can override the application level window, so you can’t give events directly to the activity, can you? All are overwritten, so you should give the event to dialog at this time.

For convenience, we useDecorViewThis role acts as the first element of distribution. It is up to him to find the current interface window, so it is also found in the codemWindow.getCallback()In fact, it is the corresponding activity or dialog.

  • Secondly, after giving it to acitivity, why not give it directly to the top-level view – decorview to start distributing events?

becauseActivityandDecorViewThere is no direct relationship between them. How did decorview come from? It is created through setcontentview, so decorview cannot be seen in the activity. The instance of decorview is saved in phonewindow and managed by window.

thereforeActivityEvents must be managed by window. It has been said that phonewindow’s accusation is to help activity manage view, so it is also its responsibility to distribute events to it. andPhoneWindowThe way to deal with it is to give it to the topDecorViewHere we go.

In this way, a chain of event distribution is formed:

DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup

Give it to the person who does the specific task (ViewGroup)

Then we begin to assign tasks, that isViewGroupThis part of the content is a cliche. The most important thing is thisdispatchTouchEventmethod.

Assuming we haven’t seen the source code, when an event comes, there will be a variety of possibilities of transmission interception. I drew a brain map:

1.png

Questions raised include:

  • ViewGroupWhether to intercept the event and how to deal with it after interception?
  • Don’t intercept and give it toSubviewperhapsChild ViewGroupHow to deal with it?
  • SubviewHow to decide whether to intercept?
  • SubviewHow to handle the event after interception?
  • SubviewAfter the event is not interceptedParent element ViewGroupHow to deal with the incident?
  • ViewGroupNo interception,SubviewNo interception, how to deal with the final event?

Next, it will be analyzed in detail.

Whether the ViewGroup intercepts events and how to handle them after interception?

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

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

        //2    
        if (!canceled && !intercepted) {
            //Event passed to 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);
        }
    }

The above code is divided into three partsViewGroupWhether to intercept or not, and no longer pass it after interception,ViewGroupProcessing after interception.

1. Is ViewGroup blocked

As you can see, a variable is initializedintercepted, representativeviewGroupWhether to intercept.

If either of the two conditions is met, discuss whether the ViewGroup is intercepted:

  • Event isACTION_DOWN, that is, press the event.
  • mFirstTouchTargetNot null

amongmFirstTouchTargetIt is a linked list structure, which means that a child element has successfully consumed the event, so if mfirsttouchtarget is null, it means that there is no child view consumption event, which will be discussed in detail later.
When you first enter this method, the event must beACTION_DOWN, so you enter the if statement. At this time, you get a variable called disallowintercept (interception is not allowed). For the time being, press the table below, and then look.
Then assign the intercepted value toonInterceptTouchEventMethod, we can understand that whether the ViewGroup intercepts depends on the onintercepttouchevent method.

2. No delivery after interception

If the ViewGroup intercepts, that isinterceptedIf it is true, there is no need to pass it to the child view or child ViewGroup.

3. Processing after ViewGroup interception

IfmFirstTouchTargetNull indicates that there is no child view to intercept, and then turn to executiondispatchTransformedTouchEventMethod, representing that the ViewGroup wants to distribute it again.

The question here is why not judge directlyinterceptedAnd? I have to judge thismFirstTouchTarget

  • becausemFirstTouchTarget==nullNot only does the ViewGroup want to consume events by itself, but alsoViewGroupNo consumption andSubviewThere is no consumption event. Both cases will be implemented here.

that isViewGroupIf it is intercepted or the subview is not intercepted, it will be calleddispatchTransformedTouchEventMethod, in which thesuper.dispatchTouchEvent

Super represents the parent class view of ViewGroup, that is, ViewGroup will be executed as an ordinary viewView.dispatchTouchEventMethod. As for what this method does, we’ll see it together with the event handling of view later.

Through the above analysis, we can conclude thatViewGroupIntercepted pseudocode:

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

If it is a ViewGroup, it will execute toonInterceptTouchEventMethod to determine whether to intercept. If so, execute the operation of the parent class viewdispatchTouchEventmethod.

After the ViewGroup is not intercepted, it is handed over to the child view or child ViewGroup for processing?

Go onViewGroupIf it is not intercepted, it will also be transmitted to the child view:

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

ViewGroupIf not intercepted, theninterceptedIf it is false, it will enter the above if statement.

It is also divided into three parts: traversing the sub view, judging the event coordinates and passing the event

1. Traversal subview

The first part is to traverse all the child views of the current ViewGroup.

2. Judge event coordinates

Then it will judge whether the event is within the coordinates of the current sub view. If the place the user touches is not the current view, it is not necessary to distribute the view. Another condition is that the current view is not in the animation state.

3. Delivery event

If the event coordinate is within this view, start to deliver the event and call the dispatchtransformedtouchevent method. If it is true, call the addtouchtarget method to record the event consumption chain.

dispatchTransformedTouchEventIs the method a little familiar? Yes, it happened just now. Look again:

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

It’s right herechildMade a judgment, thischildnamelySubview, if the child view is not null, the of the child view is calleddispatchTouchEventMethod to continue distributing events. If it is null, it is the case just now. Call the parent classdispatchTouchEventMethod. The default is to consume events by yourself.

Of course, the child may be ViewGroup or view. In short, continue to distribute callsSubviewperhapsChild ViewGroupMethods.

Here, one aboutdispatchTouchEventThe recursion of is shown:
If a ViewGroup cannot consume events, it will be passed to the dispatchtouchevent method of the child view / child ViewGroup. If it is a ViewGroup, it will repeat this operation until a view / ViewGroup consumes events.

Finally, ifdispatchTransformedTouchEventIf the method returns true, it means that a child view consumes the event, and then it will be called toaddTouchTargetmethod:

In this method, themFirstTouchTargetThe single linked list is assigned a value to record the consumption chain (but in the case of single touch, the structure of the single linked list is not used, just as an ordinary touchtarget object, which will be discussed later), and then break exits the cycle.

Next, let’s take a look at the logic of handling events inside view.

How do sub views handle events and whether to intercept them?

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

There are actually two logics:

  • 1. If view is setsetOnTouchListeneralsoonTouchMethod returns true, thenonTouchEventWill not be executed.
  • 2. Otherwise, executeonTouchEventmethod.

Therefore, it will be executed directly by defaultonTouchEventmethod.

For the event distribution of view, we can also write a piece of pseudo code and addsetOnClickListenerMethod call:

public void consumeEvent(MotionEvent event) {
    if (!setOnTouchListener || !onTouch) {
        onTouchEvent(event);
    } 

    if (setOnClickListener) {
        onClick();
    }
}

How to handle the event after the subview is intercepted?

After the child view is intercepted, it will be given to the single linked listmFirstTouchTargetAssignment.

This has just been said. The logic is in the addtouchtarget method. Let’s take a specific look:

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

How is this single linked list connected? We said beforedispatchTouchEventIs a recursive process. When a child view consumes an event, theaddTouchTargetMethod, will letmFirstTouchTargetThe child value of points to the child view, and then goes up. Finally, it will be spliced into a single linked list structure, and the tail node is the view consumed.

Why is it similar? becausemFirstTouchTargetNot really connected, but through each ViewGroupmFirstTouchTargetIndirectly.

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

A
   / \
  B   C
    /  \
   D    E

A. B and C are ViewGroup, D and E are view.

When the point we touch is in viewd, the order of event distribution isA-C—D

When C traverses D, viewd consumes events, so it goes to the addtouchtarget method and wraps a file containing viewdTouchTarget, we call it targetd.

Then set the of CmFirstTouchTargetIs targetd, that is, its child value is viewd.

Return to the previous layer, that is, layer A. because D consumes events, CdispatchTouchEventMethod also returns true, which is also calledaddTouchTargetMethod, a targetc is wrapped.

Then a will be setmFirstTouchTargetIs targetc, that is, its child value is viewc.

The final distribution structure is:

A.mFirstTouchTarget.child -> C

C.mFirstTouchTarget.child -> D

SomFirstTouchTargetFind the view at the lower level of the consumption chain through child, and then continue to find the view at the lower level through child, and then record the complete path of consumption.

thatmFirstTouchTargetWhere is the linked list structure used? Multi touch.

For multi touch and different clicking targets,mFirstTouchTargetWill exist as a linked list structure. Next points to the touchtarget object created when the previous finger is pressed.

In the case of single touch,mFirstTouchTargetThe linked list will degenerate into a single listTouchTargetObject:

  • mFirstTouchTarget.nextAlways null.
  • mFirstTouchTarget.childAssign the value to the view of the next layer of the consumption chain, and recursively call mfirsttouchtarget.child of each layer until the view of consumption.

Finally, every action_ When the down event comes, mfirsttouchtarget will be reset to welcome a new round of event sequence.

How does the ViewGroup handle events after the child view does not intercept events?

If the child view does not intercept events, thenmFirstTouchTargetFor null, after the loop is dropped, it is called.dispatchTransformedTouchEventmethod.

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

Finally calledsuper.dispatchTouchEvent, that isView.dispatchTouchEventmethod.

Can seeSubviewDo not intercept events andViewGroupThe processing of interception events is the same, and they will all come to this method.

So what does this method do? The processing method of view is mentioned abovedispatchTouchEventAs already mentioned, it is still the pseudo code, but here view is the parent class of ViewGroup.

So, to summarize, if allSubviewDo not process events, then:

  • Default executionViewGroupofonTouchEventmethod.
  • If setViewGroupofsetOnTouchListener, it will be executedonTouchmethod.

The ViewGroup is not intercepted and the child views are not intercepted. How to deal with the final event?

Finally, ifViewGroupNo interception,SubviewNo interception, which meansmFirstTouchTarget == nullAt the same time,dispatchTransformedTouchEventMethod also returns false.

In a word, it is the name of all viewgroupsdispatchTouchEventMethods all return false. What should I do at this time? Back to the beginning of the big guys’ meeting:

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

Yes, ifsuperDispatchTouchEventIf the method returns false, the activity will be executedonTouchEventmethod.

Summary

To summarize:

  • The essence of event distribution is a recursive method, which is called by passing downdispatchTouchEventMethod to find the handler of the event, which is common in projectsResponsibility chain model

  • In the process of consumption, ViewGroup’s processing method isonInterceptTouchEvent

  • In the process of consumption, the processing method of view isonTouchEventmethod.

  • If the underlying view does not consume, the parent element is executed step by steponTouchEventmethod.

  • If all viewsonTouchEventIf all methods return false, the activity will be executed in the endonTouchEventMethod, the event distribution ends.

Complete event consumption pseudo code:

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 you do not intercept and the child view does not consume, you should also call the consumption method
        isConsume = consumeEvent(event);
    }
    return isConsume;
}


public void consumeEvent(MotionEvent event) {
    //The logic of your own consumption event will call ontouchevent by default
    if (!setOnTouchListener || !onTouch) {
        onTouchEvent(event);
    } 
}

dispatchTouchEvent() + onInterceptTouchEvent() + onTouchEvent(), you can also take these three methods as the focus of understanding memory event distribution.

Subsequent task processing (event sequence)

Finally, the task found its owner. It seems that the process is over, but there is still a problem: how to deal with the subsequent tasks after the task? For example, you need to add the function of so and so module.

Can’t you go through the company process again? If we follow the normal logic, we should find the person who was in charge of our task to continue to deal with it and have a lookAndroid Inc Did you do that.

OneMotionEventThe event sequence generally includes:

ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL

What we all said just now isACTION_DOWN, that is, how to handle the event when the mobile phone is pressed, so how to deal with the subsequent mobile phone leaving the screen?

Suppose there was one beforeACTION_DOWNAnd consumed by a child, somFirstTouchTargetThere will be a complete point, and here comes the second event——ACTION_MOVE

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

Then you’ll find out,ACTION_MOVEThe incident doesn’t go in at allSubviewInstead, it goes directly to the last logic:

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

IfmFirstTouchTargetNull, which means that you can go to the ViewGroup itself as mentioned earlieronTouchEventmethod.

This is obviously not null, so go to else and start traversing againmFirstTouchTarget, as I said before, when using single touch,target.nextNull,target.childIt is the view at the next level of the consumption chain, so it actually gives the event to the view at the next level.

There is a point that many friends may not have noticed before, that is, whenACTION_DOWNWhen you go here, you will find the view execution of the consumption through mfirsttouchtargetdispatchTransformedTouchEvent
But before that, traversalViewIt has been implemented oncedispatchTransformedTouchEventMethod, do you want to execute it againdispatchTransformedTouchEventHow?
Isn’t that repeated?

  • This involves another variablealreadyDispatchedToNewTouchTarget。 This variable represents whether a view consumption event has been executed before. When the event isACTION_DOWN, it will traverse the view. If the view consumes events, thenalreadyDispatchedToNewTouchTargetIt is assigned to true, so it will not be executed again here, and it will be executed directlyhandled = true

thereforefollow-up taskThe processing logic of is also basically understood:

As long as a view starts processing intercepted events, the whole event sequence can only be handled by it.

Optimize task dispatch process (resolve sliding conflicts)

At this point, the task was finally distributed. After the task was completed, the group held a meetingWrap up meeting

In fact, the task distribution process can be optimized. For example, some tasks are not necessarily assigned to only one person, such as two people. Give a the tasks a is good at and B the tasks B is good at to maximize the use of everyone.

But our previous logicdefaultYes, the task is handed over to a, and all subsequent tasks will be handed over to a. Therefore, it is necessary to design a mechanism to intercept some tasks.

In fact, this involvesSliding conflictFor example, a scenario:

OutsideViewGroupIt moves laterally, not internallyViewGroupIt needs to move vertically, so it needs toACTION_MOVEJudge and intercept the event. (similar to ViewGroup + fragment + recyclerview)

Directly speaking, there are two solutions for Android:

  • External interception method.
  • Internal interception method.

External interception method

The external interception method is relatively simple, because it will be executed every time whether the sub view is intercepted or notonInterceptTouchEvnetMethod, so we can choose whether to intercept events according to our own business conditions in this method.

//External interception method: parent view.java      
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        //Parent view interception 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;

    }

The logic is very simple, which is based on business conditionsonInterceptTouchEventThis method determines whether to intercept. Because this method controls whether to intercept in the parent view, this method is called external interception method.

But this conflicts with our previous cognition, ifACTION_DOWNIf it is handed over to the child view for processing, subsequent events should be directly distributed to this view. Why can it be intercepted by the parent view?

Let’s see againdispatchTouchEventmethod:

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

When the event isACTION_MOVEWhen, and inonInterceptTouchEventMethod returns true, so hereintercepted=true, and then to the following logic,cancelChildThe value of is also true, and then passed todispatchTransformedTouchEventMethod, yes, it’s this method again. The difference iscancelChildThe sub segment is true.

The name of this field must be related to canceling the sub view event. Continue to see:

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);
            return handled;
        }
    }

See, when the second field cancel is true, the event will be modified toACTION_CANCEL!!, Then it will be passed on.

So even if a view is consumedACTION_DOWNHowever, when subsequent events come, in the parent elementonInterceptTouchEvent()If true is returned in, the event will be modified toACTION_CACLEThe event is then passed to the child view.

thereforeSubviewOnce again handed over the control of the event sequence, which is the reason why the external interception method can be realized.

Internal interception method

Continue to look at the internal interception method:

//Parent view.java            
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

    //Subview.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //Parent view interception 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);
    }

The internal interception method is to give the initiative to the child view. If the child view needs events, it will be consumed directly. Otherwise, it will be handed over to the parent container for processing. We list the following two cases: down and move:

  • ACTION_DOWNThe child view must be able to consume, so the parent viewonInterceptTouchEventTo return false, otherwise it will be intercepted by the parent view, and subsequent events will not be transmitted to the child view.
  • ACTION_MOVEWhen the parent viewonInterceptTouchEventThe method should return true, indicating that when the child view does not want to consume, the parent view can consume in time. How can the child view be controlled? You can see that the code sets arequestDisallowInterceptTouchEventMethod, 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;
        }
    }

Such passage|=and&= ~ Operator parameter modification is a common method of setting ID in the source code:

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

Therefore, it is set when the parent element interception is requiredrequestDisallowInterceptTouchEvent(false)Method to set the flag bit to 0 so that the parent element can execute to the onintercepttuchevent method.

The specific effective code is indispatchTouchEventMethod:

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

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

As you can see, ifdisallowInterceptIf it is false, it means that the parent view wants to intercept, and then it will execute toonInterceptTouchEventMethod, inonInterceptTouchEventMethod, and the parent view is intercepted successfully.

summary

After the visit of thumb reporter, I finally figured out the Android company’s handling of events and tasks. I hope it can help you in front of the screen. See you next time.

reference resources

Exploration of Android development Art
Daily question: did the event arrive at decorview or window first?
Input system – whole process of event handling
Reflection on the design and implementation of Android event distribution mechanism
View · inputevent event delivery source code analysis
Thoroughly grasp the event distribution sequence of Android touch

bye-bye

Thank you for reading. A little buddy can learn about my official account code blocks. ❤️❤️
One knowledge point a day makes a lot, and a knowledge architecture is established.
There are a group of good Android partners here. Welcome to join ~