In the last article, we learned the drawing process of View from the perspective of the interviewer. In this article, we will talk about android event distribution.

First, topic level

DecorView -> ViewGroup -> View dispatchTouchEvent What knowledge points can be drawn out deeply from this?

  1. How does an event get from the screen click to the Activity?
  2. When is the CANCEL event triggered?
  3. How do I resolve sliding conflicts?

Second, detailed explanation of the topic

2.1 Distribution of Android events

Android event dispatch will probably go through Activity -> PhoneWindow -> DecorView -> ViewGroup -> View’s dispatchTouchEvent. DispatchTouchEvent can be explained with the following pseudocode, the process is not specific analysis, we should also be relatively clear.

/ / pseudo code
public boolean dispatchTouchEvent(a) {
    boolean res = false;

    // Whether intercepting events is not allowed
    / / if set FLAG_DISALLOW_INTERCEPT, not intercept events, so the child can intercept events by requestDisallowInterceptTouchEvent control whether the parent View
    final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;

    if(! disallowIntercept && onInterceptTouchEvent()) {// View does not call here, but executes the following touchListener judgment directly
        if (touchlistener && touchlistener.onTouch()) {
            return true;
        }
        res = onTouchEvent(); -> performClick() -> clickListener.onclick ()
    } else if (DOWN) { // If the event is DOWN, the sub-view is iterated for event distribution
        // Loop the child View to handle events
        for(childs) { res = child.dispatchTouchEvent(); }}else {
        // The event is sent to the target for processing. In this case, the target is the View that handled the DOWN event last step
        target.child.dispatchTouchEvent();
    }
    return res;
}
Copy the code

2.2 How does an event arrive at an Activity

Since the event distribution above starts with the Activity, how does the event get to the Activity?

The general process looks like this: When the user clicks on the device, the Linux kernel accepts the interrupt, which is processed into input event data and written to the corresponding device node. InputReader monitors all device nodes under /dev/inpu/. When a node has data to read, EventHub takes out the original event and translates it into an input event, which is delivered to InputDispatcher. InputDispatcher delivers the event to the appropriate window according to the window information provided by WMS, and the window ViewRootImpl dispatches the event

The general flow chart is as follows:

There are mainly several stages:

  1. Hardware interrupt
  2. InputManagerService Does something
  3. InputReaderThread
  4. InputDispatcherThread does something
  5. WindowInputEventReceiver
2.2.1 Hardware Interruption

Hardware interrupts are briefly introduced here. The operating system receives hardware events through interrupts. The kernel registers the address of the interrupt type and the corresponding handling method in the interrupt descriptor table at startup. When there is an interrupt, the corresponding handler is called to write the corresponding event to the device node.

2.2.2 What InputManagerService does

InputManagerService is used to handle Input events. The Java side of InputManagerService is a wrapper around the C++ code and provides some callback to pass events to the Java layer. Let’s take a look at the InputManagerService initialization code on the Native side.

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    // ...
    sp<EventHub> eventHub = new EventHub(a); mInputManager =new InputManager(eventHub, this.this);
}
Copy the code

There are two main things to do:

  1. Initialize EventHub
EventHub::EventHub(void) {
            // ...
    mINotifyFd = inotify_init(a);int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
Copy the code

The EventHub function is to monitor whether device nodes are updated. 2. Initialize InputManager

void InputManager::initialize(a) {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
Copy the code

InputManager initializes two threads, InputReaderThread and InputDispatcherThread. One is used to read events and the other is used to dispatch events.

2.2.3 InputReaderThread
bool InputReaderThread::threadLoop(a) {
    mReader->loopOnce(a);return true;
}

void InputReader::loopOnce(a) {
    // Get events from EventHub
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    // Handle events
    processEventsLocked(mEventBuffer, count);
    // Events are sent to InputDispatcher for distribution
    mQueuedListener->flush(a); }Copy the code

There’s a lot of code here, so I’m going to omit it. InputReaderThread does three things:

  1. Get the event from EventHub
  2. Handle events, where there are different types of events that are handled and encapsulated differently
  3. Send events to InputDispatcher
2.2.4 InputDispatcherThread Does
bool InputDispatcherThread::threadLoop(a) {
    mDispatcher->dispatchOnce(a);// Call dispatchOnceInnerLocked internally
    return true;
}

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    // Retrieve an event from the queue
    mPendingEvent = mInboundQueue.dequeueAtHead(a);// Perform different operations according to different event types
    switch (mPendingEvent->type) {
    case EventEntry::TYPE_CONFIGURATION_CHANGED: {
        // ...
    case EventEntry::TYPE_DEVICE_RESET: {
        // ...
    case EventEntry::TYPE_KEY: {
        // ...
    case EventEntry::TYPE_MOTION: {
        // Send events
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break; }}Copy the code

Events are dispatched via dispatchMotionLocked. The function calls are omitted as follows:

dispatchMotionLocked -> dispatchEventLocked -> prepareDispatchCycleLocked -> enqueueDispatchEntriesLocked -> startDispatchCycleLocked -> publishMotionEvent -> InputChannel.sendMessage
Copy the code

It finds the currently appropriate Window and calls InputChannel to send the event.

The InputChannel here corresponds to the InputChannel in view owl pl. As for the middle of how to do the correlation, here will not do the analysis, the whole code is relatively long, and for the grasp of the process has little impact.

2.2.5 WindowInputEventReceiver Receives events and distributes them

In view Windows PL there is a WindowInputEventReceiver for receiving events and distributing them. Events sent by InputChannel are finally accepted by the WindowInputEventReceiver. The WindowInputEventReceiver is initialized in viewrootimpl. setView. Call setView is the ActivityThread. HandleResumeActivity – > WindowManagerGlobal. AddView.

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        // ...
        if(mInputChannel ! =null) {
            if(mInputQueueCallback ! =null) {
                mInputQueue = new InputQueue();
                mInputQueueCallback.onInputQueueCreated(mInputQueue);
            }
            mInputEventReceiver = newWindowInputEventReceiver(mInputChannel, Looper.myLooper()); }}Copy the code
public abstract class InputEventReceiver {
    // The native side code calls this method and dispatches the event
    private void dispatchInputEvent(int seq, InputEvent event, int displayId) { mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event, displayId); }}final class WindowInputEventReceiver extends InputEventReceiver {
    @Override
    public void onInputEvent(InputEvent event, int displayId) {
        // Event accept
        enqueueInputEvent(event, this.0.true);
    }
    // ...
}

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    // Whether to process the event immediately
    if (processImmediately) {
        doProcessInputEvents();
    } else{ scheduleProcessInputEvents(); }}void doProcessInputEvents(a) {
    // ...
    while(mPendingInputEventHead ! =null) {
        deliverInputEvent(q);
    }
    // ...
}

private void deliverInputEvent(QueuedInputEvent q) {
    // ...
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    // Distribute events
    stage.deliver(q);
}
Copy the code

From the code flow above, events eventually go to inputStage.Deliver.

abstract class InputStage {
    public final void deliver(QueuedInputEvent q) {
        if((q.mFlags & QueuedInputEvent.FLAG_FINISHED) ! =0) {
            forward(q);
        } else if (shouldDropInputEvent(q)) {
            finish(q, false);
        } else{ apply(q, onProcess(q)); }}}Copy the code

In Deliver, the onProcess is finally called, and the implementation is 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) {
                return processPointerEvent(q);
            } else if((source & InputDevice.SOURCE_CLASS_TRACKBALL) ! =0) {
                return processTrackballEvent(q);
            } else {
                returnprocessGenericMotionEvent(q); }}}private int processPointerEvent(QueuedInputEvent q) {
        / / here is mView DecorView, call to DecorView. DispatchPointerEvent
        boolean handled = mView.dispatchPointerEvent(event);
        // ...
        returnhandled ? FINISH_HANDLED : FORWARD; }}// View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        returndispatchGenericMotionEvent(event); }}// DecorView.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    // Call mwindow.setcallback (this) in activity. attach; Set up the
    final Window.Callback cb = mWindow.getCallback();
    returncb ! =null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
Copy the code

Through the above a series of processes, finally is invoked to the Activity. The dispatchTouchEvent, also is the start of the process.

From the above analysis, we basically know the process of the event from the user clicking the screen to the View, which is shown below.

2.3 When will the CANCEL event be triggered

If you look closely at the dispatchTouchEvent code, you can see some moments:

  1. After the View receives an ACTION_DOWN event, the previous event has not ended (the system may discard subsequent events due to APP switchover or ANR). In this case, the View will perform an ACTION_CANCEL first
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev); resetTouchState(); }}Copy the code
  1. The child View intercepts the event, but the parent View intercepts the event again, which sends the ACTION_CANCEL event to the child View
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mFirstTouchTarget == null) {}else {
        // A child View got the event
        TouchTarget target = mFirstTouchTarget;
        while(target ! =null) {
            final TouchTarget next = target.next;
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            CancelChild is true if the parent View intercepts the event
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true; }}}}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final int oldAction = event.getAction();
    // If cancel is true, an ACTION_CANCEL event is sent
    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

2.4 How to Resolve sliding conflicts

This is also a platitude of a problem, there are two main methods:

  1. Intercepting sliding events by overriding onInterceptTouchEvent of the parent class
  2. By calls to the parent in subclasses. RequestDisallowInterceptTouchEvent whether to notify the parent to intercept events, RequestDisallowInterceptTouchEvent FLAG_DISALLOW_INTERCEPT flag, this done at the beginning of pseudo code there is introduced

Third, summary

The above is from the View event distribution derived from some questions, simple answers are as follows:

  1. View Event Distribution
/ / pseudo code
public boolean dispatchTouchEvent(a) {
    boolean res = false;

    // Whether intercepting events is not allowed
    / / if set FLAG_DISALLOW_INTERCEPT, not intercept events, so the child can intercept events by requestDisallowInterceptTouchEvent control whether the parent View
    final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;

    if(! disallowIntercept && onInterceptTouchEvent()) {// View does not call here, but executes the following touchListener judgment directly
        if (touchlistener && touchlistener.onTouch()) {
            return true;
        }
        res = onTouchEvent(); -> performClick() -> clickListener.onclick ()
    } else if (DOWN) { // If the event is DOWN, the sub-view is iterated for event distribution
        // Loop the child View to handle events
        for(childs) { res = child.dispatchTouchEvent(); }}else {
        // The event is sent to the target for processing. In this case, the target is the View that handled the DOWN event last step
        target.child.dispatchTouchEvent();
    }
    return res;
}
Copy the code
  1. How does an event get from the screen click to the Activity?

  1. When is the CANCEL event triggered?
  • After the View receives an ACTION_DOWN event, the previous event has not ended (the system may discard subsequent events due to APP switchover or ANR). In this case, the View will perform an ACTION_CANCEL first
  • The child View intercepts the event, but the parent View intercepts the event again, which sends the ACTION_CANCEL event to the child View
  1. How do I resolve sliding conflicts?
  • Intercepting sliding events by overriding onInterceptTouchEvent of the parent class
  • By calls to the parent in subclasses. RequestDisallowInterceptTouchEvent whether to notify the parent to intercept events

The article is constantly updated. Search “ZYLAB” on wechat and get the update immediately. Reply to “Simulated interview” to unlock the one-to-one interview experience of Big factory