Thumb reporter went deep into Android company to inquire about the secrets behind the event distribution mechanism

Posted by Stryks on Mon, 07 Mar 2022 23:38:40 +0100

preface

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

For example, how to get input events across processes? Is there an InputStage responsibility chain before the dispatchTouchEvent responsibility chain? Transfer order between DecorView and PhoneWindow?

In addition, it also includes the processing method of event sequence in the process of event distribution? Coordination between ViewGroup and View? mFirstTouchTarget true and false linked list? wait.

All this starts with your lovely little thumb

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 large company (Android company), and our task of touching the mobile phone is a complete project demand. Today, let's go deep into the Android company to find out the secrets of the event distribution.

Before that, I also listed the questions and outline:

Hardware department and kernel Department

First, my thumb found the Android company and spoke out my needs, such as clicking on a View and sliding to another location.

Android will send the hardware department to talk with my thumb. After receiving my demand, 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 the / dev/input / directory of a management system within the company.

The purpose of turning the internal requirements into the external ones is to understand them.

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 keep listening to the / dev/input / directory and deal with new events when they are found.

So where is this task processing department?

I don't know if you remember starting a series of system related services in the process of SystemServer, such as AMS, PMS and so on. There is also a less prominent role called InputManagerService.

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

Internally, a read thread, i.e. InputReader, will be started. It will get tasks from the management system, i.e. the / dev/input / directory, distribute them to the InputDispatcher thread, 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 project team specialized in processing the task, 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 to the setView method of ViewRootImpl:

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, an InputChannel object is created and enters the systemserver process through Binder, and finally forms the client of socket.

This involves the knowledge of socket communication, and the more important is the socketpair method of c layer.

The socketpair() function is used to create a pair of unnamed, 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:

  • Save socket server to system_ Mainputchannel of WindowState in server;
  • The socket client passes it back to the Main UI thread ViewRootImpl's mInputChannel of the remote process through the binder;

If you are interested, you can see gityuan's blog on input analysis. There is a link at the end of the article.

So to summarize, an object InputChannel is created in the App process and passed into the SystemServer process through the Binder mechanism, that is, WindowManagerService. Then, a pair of sockets are created in WindowManagerService for inter process communication, and the transmitted InputChannel points to the client of the socket.

Then the main thread of the App process will listen to the socket client and call back nativeinputeventreceiver after receiving the message (output event) The handleevent () method will eventually go to inputeventreceiver Dispachinputevent method.

dispachInputEvent, which handles 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 reaches the project team, it will be distributed within the team first. Here, the first responsibility chain distribution mode will be involved.

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 is, InputStage 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();
    }
}

After wandering around, I didn't expect to come to ViewRootImpl, so ViewRootImpl is not only responsible for the drawing of the interface, but also responsible for some processing of event distribution.

The enqueueInputEvent method here involves a QueuedInputEvent class, which is an event class that encapsulates InputEvent, and then calls the doProcessInputEvents method through assignment:

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

Here, the logic seems to be gradually clear. QueuedInputEvent is an input event, InputStage is the responsibility chain for handling input events, and the next field represents the next InputStage of the responsibility chain.

So what exactly did InputStage do? Return to the setView method of ViewRootImpl and have a look:

	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 stage, the delay queue is mainly constructed.
  • EarlyPostImeInputStage. Early processing stage of input method.
  • ImeInputStage. In the input method event processing stage, the input method characters are processed.
  • 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 and so on.

After the event is distributed, it will inform the InputDispatcher thread of the SystemServer process, and finally remove the event to complete the distribution and consumption of this event.

Our view finger touch event occurs in the ViewPostImeInputStage stage. 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 execute the dispatchTouchEvent method of mView, and this mView is DecorView, which is also assigned in setView. I won't elaborate.

So far, we have finally reached the familiar link, the dispatchTouchEvent method.

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 takes place in the conversation between several leaders, namely DecorView, PhoneWindow and 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);
    }    

It can be seen that from DecorView, events pass through Activity, PhoneWindow and DecorView in turn.

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 is more than one form of Activity on the interface. If there is a Dialog on the interface, and the Window of Dialog belongs to a child Window, it can overwrite the application level Window. Therefore, you can't directly hand over the event to the Activity? All are overwritten, so the event should be handed over to Dialog at this time.

For convenience, we use the role of DecorView as the first element of distribution. It is up to him to find the holding of the current interface window, so mwindow is also found in the code Getcallback(), which is actually 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?

Because there is no direct relationship between Activity and DecorView. 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.

Therefore, the events of an Activity must be managed by Window. It has been said before that PhoneWindow's accusation is to help the Activity manage the View, so it is also its responsibility to distribute the events to it. The processing method of PhoneWindow is to leave it to the DecorView at the top level.

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)

Next, we will start to assign tasks, that is, the event distribution time of ViewGroup. This part is an old saying. The most important thing is this dispatchTouchEvent method.

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:

Questions raised include:

  • Whether the ViewGroup intercepts events and how to deal with them after interception?
  • How to deal with it if it is not intercepted and handed over to the sub View or sub ViewGroup?
  • How does the subview decide whether to intercept?
  • How to handle the event after the sub View is intercepted?
  • How does the parent element ViewGroup handle events after the child View does not intercept events?
  • The View Group is not intercepted and the child views are not intercepted. How to deal with the final event?

Next, it will be analyzed in detail.

Whether the ViewGroup intercepts events and how to deal with 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 parts: whether the ViewGroup is intercepted, whether it will not be transmitted after being intercepted, and the processing after being intercepted by the ViewGroup.

1. Is ViewGroup blocked

You can see that a variable intercepted is initialized to indicate whether the viewGroup is intercepted.

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

  • The event is ACTION_DOWN, that is, press the event.
  • mFirstTouchTarget is not null

Among them, mFirstTouchTarget is a linked list structure, which means that a child element has successfully consumed the event. Therefore, if mFirstTouchTarget is null, it means that there is no child view consumption event, which will be discussed in detail later.
When entering this method for the first time, the event must be ACTION_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 to the result of onintercepttuchevent method. We can understand that whether viewGroup intercepts depends on onintercepttuchevent method.

2. No delivery after interception

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

3. Processing after ViewGroup interception

If mFirstTouchTarget is null, it means that there is no child View to intercept, and then turn to the method of dispatchTransformedTouchEvent, which means that the ViewGroup wants to distribute it again.

Here's a question: why not judge intercepted directly? Do you have to judge this mFirstTouchTarget?

  • Because mFirstTouchTarget==null not only means that the ViewGroup wants to consume the event itself, but also means that the ViewGroup does not consume the event and the child View does not consume the event. Both cases will be executed here.

That is, if the ViewGroup intercepts or the child View does not, it will call the dispatchTransformedTouchEvent method. In this method, super will be called at last dispatchTouchEvent.

super represents the parent class View of ViewGroup, that is, ViewGroup will execute View as an ordinary View Dispatchtouchevent method. As for what this method does, we will see it together with the event processing of View later.

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

If it is a ViewGroup, it will first execute the onInterceptTouchEvent method to determine whether to intercept it. If it is intercepted, it will execute the dispatchTouchEvent method of the parent class View.

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

Then, if the ViewGroup does not intercept, it will also be transferred 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;
                    }
                }
            }
        }
    }

If the ViewGroup does not intercept, then intercepted is false, and the above if statement will be entered.

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 View Group.

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.

Is the dispatchTransformedTouchEvent 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);
        }
    }

Here, we judge the child passed in. This child is the child View. If the child View is not null, call the dispatchTouchEvent method of the child View to continue distributing events. If it is null, it is the case just now. Call the dispatchTouchEvent method of the parent class and consume the event by itself by default.

Of course, the child may be a ViewGroup or a View. In short, continue to distribute and call the methods of the child View or child ViewGroup.

At this point, a recursion about dispatchTouchEvent appears:
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, if the dispatchTransformedTouchEvent method returns true, it means that a child view has consumed the event, and then it will call the addTouchTarget method:

In this method, the single linked list mFirstTouchTarget 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 mentioned later), and then break exits the loop.

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 setOnTouchListener is set in View and the onTouch method returns true, onTouchEvent will not be executed.
  • 2. Otherwise, execute the onTouchEvent method.

Therefore, by default, the onTouchEvent method will be executed directly.

For the event distribution of View, we can also write a piece of pseudo code, and add the call of setOnClickListener method:

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

    if (setOnClickListener) {
        onClick();
    }
}

How to handle the event after the sub View is intercepted?

After the child View is intercepted, it will assign a value to the single linked list mFirstTouchTarget.

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? As we said before, dispatchTouchEvent is a recursive process. When a child View consumes an event, through the addTouchTarget method, the child value of mFirstTouchTarget will point to that child View, and then go up. Finally, it will be spliced into a structure similar to a single linked list, and the tail node is the View consumed.

Why is it similar? Because mFirstTouchTarget is not really connected, but indirectly connected through mFirstTouchTarget of each ViewGroup.

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 is A-C-D.

When C traverses D, ViewD consumes events, so we go to the addTouchTarget method and wrap a TouchTarget containing ViewD, which we call TargetD.

Then set mFirstTouchTarget of C as TargetD, that is, its child value is ViewD.

Return to the previous layer, that is, layer A. because D consumes events, the dispatchTouchEvent method of C also returns true. Similarly, it calls the addTouchTarget method to wrap a TargetC.

Then, mFirstTouchTarget of A will be set to TargetC, that is, its child value will be ViewC.

The final distribution structure is:

A.mFirstTouchTarget.child -> C

C.mFirstTouchTarget.child -> D

Therefore, mFirstTouchTarget finds the View at the lower level of the consumption chain through child, and then continues to find the View at the lower level through child, and then records the complete path of consumption.

Where is the linked list structure of mFirstTouchTarget used? Multi touch.

For multi touch and different click targets, mFirstTouchTarget will 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, the mFirstTouchTarget linked list will degenerate into a single TouchTarget object:

  • mFirstTouchTarget.next} is always null.
  • mFirstTouchTarget.child ^ is assigned as the view of the next layer of the consumption chain, and the mfirsttouchtarget of each layer is called recursively Child, until the view of consumption.

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

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

The sub View does not intercept the event, then mFirstTouchTarget is null. After the loop is dropped, the dispatchTransformedTouchEvent method is called.

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

Finally called super Dispatchtouchevent, or view Dispatchtouchevent method.

It can be seen that the sub View does not intercept events and the ViewGroup intercepts events are handled in the same way. They will all go to this method.

So what does this method do? As mentioned above, the View processing method dispatchTouchEvent is still the pseudo code, but here View is the parent class of ViewGroup.

So, to summarize, if all child views do not process events, then:

  • The onTouchEvent method of ViewGroup is executed by default.
  • If setOnTouchListener of ViewGroup is set, the onTouch method will be executed.

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

Finally, if the View Group is not intercepted, the child views are not intercepted, which means that the dispatchTransformedTouchEvent method also returns false when mFirstTouchTarget == null.

In short, all the dispatchTouchEvent methods of ViewGroup 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, if the superDispatchTouchEvent method returns false, the onTouchEvent method of the Activity will be executed.

Summary

To summarize:

  • The essence of event distribution is a recursive method. By passing it down, call the dispatchTouchEvent method to find the handler of the event, which is the common responsibility chain mode in the project.

  • In the process of consumption, the processing method of ViewGroup is onInterceptTouchEvent

  • In the process of consumption, the processing method of View is the onTouchEvent method.

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

  • If all the onTouchEvent methods of View return false, the onTouchEvent method of Activity will be executed finally, and the event distribution will be ended.

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 don't intercept and the sub View doesn't 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() + oninterceptotouchevent() + ontouchevent(), you can also focus on these three methods to understand the distribution of memory events.

Subsequent task processing (event sequence)

Finally, the task has 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, we need to add the function of so and so module.

It's impossible to go through the company process again, isn't it? 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 see if Android does so.

A MotionEvent event sequence generally includes:

ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL

We were all talking about action just now_ Down, that is, the event processing of the mobile phone pressing. How should the subsequent mobile phone leave the screen?

Suppose there is an action before_ Down and consumed by a child View, so mFirstTouchTarget will have a complete direction. At this time, the second event - action comes_ MOVE.

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

Then you will find that action_ The move event does not enter the loop method of the sub View at all, but 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;
        }
    }

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

It's obviously not null here, so go to else and start traversing mFirstTouchTarget again. As mentioned before, target Next is null, target Child is the View at the next level of the consumption chain, so in fact, the event is handed over to the View at the next level.

There is a point that many friends may not have noticed before, that is, when action_ When you go down, you will find the consuming View through mFirstTouchTarget and execute dispatchtransformed touchevent.
But before that, the dispatchTransformedTouchEvent method has been executed once when traversing the View. Do you want to execute the dispatchTransformedTouchEvent method again here?
Isn't that repeated?

  • This involves another variable, alreadyDispatchedToNewTouchTarget. This variable represents whether a view consumption event has been executed before. When the event is ACTION_DOWN, the view will be traversed. If the view consumes events, the alreadyDispatchedToNewTouchTarget will be assigned to true, so it will not be executed again here. Directly handled = true.

Therefore, the processing logic of subsequent tasks is basically understood:

As long as a View starts to handle intercepting events, the whole event sequence can only be handled by it.

Optimize task dispatch process (resolve sliding conflict)

At this point, the task was finally distributed. After the task was completed, the group held a summary 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, so as to make the best use of everyone.

However, our previous logic is that the task is handed over to A by default, and all subsequent tasks will be handed over to A. So at this time, we need to design A mechanism to intercept some tasks.

In fact, this involves the problem of sliding conflict. For example, a scenario:

The external ViewGroup moves horizontally, while the internal ViewGroup needs to move vertically, so it needs to be in action_ Judge and intercept events during move. (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. No matter whether the sub View is intercepted or not, the onInterceptTouchEvnet method will be executed every time. Therefore, in this method, we can choose whether to intercept events according to our own business conditions.

    //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. It is to decide whether to intercept in onInterceptTouchEvent according to business conditions. 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. If action_ When down 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 take another look at the dispatchTouchEvent method:

    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 is action_ When moving, and the onInterceptTouchEvent method returns true, so the intercepted=true here. Then to the following logic, the value of cancelChild is also true, and then it is passed to the dispatchTransformedTouchEvent method. Yes, it is this method again. The difference is that the cancelChild 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 changed to ACTION_CANCEL!!, Then it will be passed on.

So even if a View consumes ACTION_DOWN, but when the subsequent event comes and returns true in onInterceptTouchEvent() of the parent element, the event will be modified to action_ The case event is then passed to the child View.

Therefore, the sub View once 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_ When down, the child View must be able to consume, so the onInterceptTouchEvent of the parent View must return false, otherwise it will be intercepted by the parent View, and subsequent events will not be transmitted to the child View.
  • ACTION_ When moving, the onInterceptTouchEvent method of the parent View should return true, which means that when the child View does not want to consume, the parent View can consume in time. How can the child View control it? You can see that the code sets a requestDisallowInterceptTouchEvent method. 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;
        }
    }

This method of modifying parameters through the | = and & = ~ operators is a common method of setting identification in the source code:

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

Therefore, when the parent element needs to intercept, the requestdisallowintercepttuchevent (false) method is set, and the flag bit is set to 0, so that the parent element can execute the onintercepttuchevent method.

The specific effective 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 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;
        }
    }

It can be seen that if disallowIntercept is false, it means that the parent View wants to intercept, and then it will execute the onInterceptTouchEvent method, return true in the onInterceptTouchEvent method, and the parent View successfully intercepts.

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.

 

 

 

 

 

 

 

 

 

Do you buy watches at the counter or online

How to adjust the date of the watch

How to wear a watch comfortably on your wrist