This article is a record of preparing for the interview, mainly for what we think of the process before the event is distributed, that is, how does the event come? How did it go all the way?

The whole process of this article is mentioned in my article called “After 20 days of interviews, I finally got into Ali”. If you are interested, please click here.

So why study the ins and outs? In fact, BEFORE I wrote this article, I knew that events were transitive in the following way.

Avtivity->PhoneWindow->DecorView->ViewGroup->View

Until one day, someone asked me how did that Activity get events? I was stunned, and then I wasn’t, so I guess you know who asked me.

This article tries to describe it in a minimalist way. If you have any questions, please point out.


We will start the performance with the following classes

  1. Activity
  2. PhoneWindow extends Window
  3. WindowManager extends ViewManager
  4. WindowManagerImpl implements WindowManager
  5. WindowManagerGlobal
  6. ViewRootImpl implements ViewParent
  7. WindowManagerService extends IWindowManager.Stub
  8. DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
  9. InputChannel,InputQueue,WindowInputEventReceiver extends InputEventReceiver
  10. ViewRootHandler extends Handler

OK, we first on the conclusion, after the expansion of the description, the following part of the pure dry goods, such as before seen part of the source can directly see this part of the source code is not very familiar with the relationship, the next link is to expand the description.

The first picture to understand, the picture content is more, you can click on the original picture to enjoy the large picture without code.

    1. ActivityThread. PerformLaunchActivity () call Activity. The attach (), create PhoneWindow, create DecorView PhoneWindow.
    1. ActivityThread. HandleResumeActivity () call Activity. MakeVisible (), initialize the WindowManager and call the WindowManager. AddView ()
    1. Because the WindowManager is interface, find its implementation class and call the WindowManagerImpl. AddView ().
    1. Call WindowManagerGlobal. AddView (View View,… , Window parentWindow…)

Now need to pay attention to the view as DecorView object, the object is by * * ActivityThread handleResumeActivity () in the PhoneWindow. GetDecorView () * * to obtain, and directly assigned to the Activity of variables.

    1. Created ViewRootImpl WindowManagerGlobal. AddView (), and calls the ViewRootImpl. SetView (view,… ,…).
    1. Create an InputChannel, an InputQueue, and a WindowInputEventReceiver and pass in an InputChannel and a Looper
    1. In fact, the source of Android events is the user input behavior, which is captured by the hardware, usually saved in the dev/input node, and then assembled into KeyEvent/MotionEvent object. The Native into Java. * * InputEventReceiver dispatchInputEvent () * *.
    1. ViewRootImpl. WindowInputEventReceiver extends InputEventReceiver, joint call enqueueInputEvent () – > doProcessInputEvents – > DeliverInputEvent (q) : mFirstPostImeInputStage is ViewPostImeInputStage.
    1. ViewPostImeInputStage extends InputStage so execute onProcess(), determine if it’s a touch event, call processPointerEvent(), Then internal call mView. DispatchPointerEvent (), mView DecorView, at this time. Call and out to the DecorView dispatchTouchEvent ()
    1. Through mWindow. GetCallback () for Windows. The Callback Window. Then call the Callback. DispatchTouchEvent (), the Callback is mCallback PhoneWindow, The mCallback is assigned by attach()** to the Activity, which is naturally called to the Activity, followed by events that we all know are dispatched, and a complete loop ends.

  1. ActivityThread.performLaunchActivity()
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...if(activity ! =null) {...// You can see that the attach() method of the Activity is called here
                activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); . }}Copy the code
  • The PhoneWindow is initialized in 1.1 Activity.attach() and the CallBack object is set.
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        / / PhoneWindow initialization
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
Copy the code

2. ActivityThread. HandleResumeActivity () by PhoneWindow DecorView, then call activity. MakeVisible ()

 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {...if (r.window == null&&! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; .if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                    wm.addView(decor, l);
                } else{ a.onWindowAttributesChanged(l); }}}else if(! willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true; }...if(r.activity.mVisibleFromClient) { r.activity.makeVisible(); }}... }Copy the code
  • 2.1 PhoneWindow DecorView creation procedure
   @Override
    public final @NonNull View getDecorView(a) {
        if (mDecor == null || mForceDecorInstall) {
            / / assembly DecorView
            installDecor();
        }
        return mDecor;
    }

   private void installDecor(a) {
        mForceDecorInstall = false;
        if (mDecor == null) {
            / / get DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}}// The return value is DecorView
    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
Copy the code
  • 2.2. The activity. MakeVisible () process
  void makeVisible(a) {
        if(! mWindowAdded) { ViewManager wm = getWindowManager();// This time call its actual windowManager.addView ()
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
Copy the code
  1. WindowManager is the interface, find its implementation class and call the WindowManagerImpl. AddView ()
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
       / / this call is WindowManagerGlobal. AddView ()
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
Copy the code
  1. Initialization ViewRootImpl WindowManagerGlobal. AddView (), and then call root. SetView ()
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {...synchronized (mLock) {
           ...
           // Initialize ViewRootImpl
           root = new ViewRootImpl(view.getContext(), display);
           view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throwe; }}}Copy the code
  1. ViewRootImpl.setView(view,… ,…). Create InputChannel, InputQueue, and WindowInputEventReceiver
   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
        synchronized (this) {
            if (mView == null) { mView = view; .// Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
               if(inputChannel ! =null) {
                    if(mInputQueueCallback ! =null) {
                        // Create an object
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = newWindowInputEventReceiver(inputChannel, Looper.myLooper()); }}}Copy the code
  1. In fact, the source of Android events is the user input behavior, which is captured by the hardware, usually saved in the dev/input node, and then assembled into KeyEvent/MotionEvent object. The Native into Java. * * InputEventReceiver dispatchInputEvent () * *.

Let’s start with the InputEventReceiver

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

public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }
Copy the code

Since InputEventReceiver is an abstract class, we need to find the corresponding implementation class. Now, in step 5, the WindowInputEventReceiver is the implementation class. Therefore, we need to find the onInputEvent() method to be overwritten for the next analysis.

  1. ViewRootImpl. WindowInputEventReceiver extends InputEventReceiver, joint call enqueueInputEvent () – > doProcessInputEvents – > DeliverInputEvent (q) : mFirstPostImeInputStage is ViewPostImeInputStage.
        @Override
        public void onInputEvent(InputEvent event) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
            List<InputEvent> processedEvents;
            try {
                processedEvents =
                    mInputCompatProcessor.processInputEventForCompatibility(event);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            if(processedEvents ! =null) {
                if (processedEvents.isEmpty()) {
                    // InputEvent consumed by mInputCompatProcessor
                    finishInputEvent(event, true);
                } else {
                    for (int i = 0; i < processedEvents.size(); i++) {
                        enqueueInputEvent(
                                processedEvents.get(i), this,
                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true); }}}else {
                enqueueInputEvent(event, this.0.true); }}void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) {
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

        // Always enqueue the input event in order, regardless of its time stamp.
        // We do this because the application or the IME may inject key events
        // in response to touch events and we want to ensure that the injected keys
        // are processed in the order they were received and we cannot trust that
        // the time stamp of injected events are monotonic.
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        mPendingInputEventCount += 1;
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);

        if (processImmediately) {
            doProcessInputEvents();
        } else{ scheduleProcessInputEvents(); }}void doProcessInputEvents(a) {
        // Deliver all pending input events in the queue.
        while(mPendingInputEventHead ! =null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);

            long eventTime = q.mEvent.getEventTimeNano();
            long oldestEventTime = eventTime;
            if (q.mEvent instanceof MotionEvent) {
                MotionEvent me = (MotionEvent)q.mEvent;
                if (me.getHistorySize() > 0) {
                    oldestEventTime = me.getHistoricalEventTimeNano(0);
                }
            }
            mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);

            deliverInputEvent(q);
        }

        // We are done processing all input events that we can process right now
        // so we can clear the pending flag immediately.
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false; mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS); }}private void deliverInputEvent(QueuedInputEvent q) {...try{... InputStage stage;if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                // Return two objects of type InputStage. This InputStage is abstract, so we need to find the corresponding implementation class.
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }

            if (q.mEvent instanceof KeyEvent) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "preDispatchToUnhandledKeyManager");
                try {
                    mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
                } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}if(stage ! =null) {
                handleWindowFocusChanged();
                stage.deliver(q);
            } else{ finishInputEvent(q); }}finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Note at this point that there are quite a few implementation classes for the InputStage, but look at the end of the setView() method and focus on the assignment logic of the mFirstPostImeInputStage.

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { .... // Set up the input pipeline. CharSequence counterSuffix = attrs.getTitle(); mSyntheticInputStage = new SyntheticInputStage(); ViewPostImeInputStage InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); // To initialize and pass in the nativePostImeStage, look at the nativePostImeStage assignment logic. 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; EarlyPostImeStage mFirstPostImeInputStage = earlyPostImeStage; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; if (mView instanceof RootViewSurfaceTaker) { PendingInsetsController pendingInsetsController = ((RootViewSurfaceTaker) mView).providePendingInsetsController(); if (pendingInsetsController ! = null) { pendingInsetsController.replayAndAttach(mInsetsController); }}}Copy the code

MFirstPostImeInputStage is ViewPostImeInputStage. We need to follow up ViewPostImeInputStage to see the corresponding **onProcess()** method.

final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @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); }}}... }Copy the code

**processPointerEvent(q) **processPointerEvent(q) **processPointerEvent(q) **processPointerEvent(q) * * method

 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            // mView is a DecorView
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if(mAttachInfo.mUnbufferedDispatchRequested && ! mUnbufferedInputDispatch) { mUnbufferedInputDispatch =true;
                if(mConsumeBatchedInputScheduled) { scheduleConsumeBatchedInputImmediately(); }}return handled ? FINISH_HANDLED : FORWARD;
        }
Copy the code

Here I need to make a brief summary, to clarify the process, so that you can understand. The above mView is actually a DecorView. This object is created initially through PhoneWindow. By the WindowManager. AddView (View v…). Passed to the WindowManagerGlobal. AddView (View v..) . Then via viewrootimpl.setView (View v…) Uploaded to view WrootimPL. Therefore ViewRootImpl variable mView is DecorView, so we need to follow up DecorView dispatchPointerEvent () with DecorView extends the View, So look at the View. DispatchPointerEvent ()

 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            // Call dispatchTouchEvent() and the DecorView overrides dispatchTouchEvent().
            return dispatchTouchEvent(event);
        } else {
            returndispatchGenericMotionEvent(event); }}Copy the code

Follow up DecorView. DispatchTouchEvent ()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        returncb ! =null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
Copy the code

Note that the window. Callback cb is retrieved from mwindow.getCallback (). Where does mWindow come from? This mWindow is a PhoneWindow object. Is in ActivityThread handleResumeActivity (), Through the Activity. Window. GetDecorView () call PhoneWindow. InstallDecor () * * mDecor setWindow (this); The assignment. So you need to see where phoneWindow.getCallback ()** is assigned. In ActivityThread. PrefromLuncherActivity (), call Activity. The attach (), this method of PhoneWindow Window. The Callback for the assignment, the incoming is yourself.

 mWindow.setCallback(this);
Copy the code

. As a result, the Window Callback cb is Activity, find the Activity. The dispatchTouchEvent ()

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

At this point, how the event arrives at the Activity is fully described, and the subsequent flow is known, but I can briefly describe it.

GetWindow ().superDispatchTouchEvent(ev)

  • Phonewindow.superdispatchtouchevent (EV)

PhoneWindow call mDecor. SuperDispatchTouchEvent (event);

  • Call the DecorView.. superDispatchTouchEvent(event);

DecorView call super dispatchTouchEvent (event);

  • Because DecorView extends FrameLayout, FrameLayout extends ViewGroup, so call ViewGroup. DispatchTouchEvent ()

Through joint call ViewGroup. DispatchTransformedTouchEvent () call. Super dispatchTouchEvent (event)

  • Because ViewGroup extends View, the view.dispatchTouchEvent () event is called and passed to the View, completing the process. As for time interception, exception handling and other details will not be described in this article. Please point out any questions.

If this article is of any help to you, please click 👍🏻