阅读 676

那天有人问我,Android 的事件到底是怎么来的?

本文是楼主准备面试前的一个记录, 主要针对的是 我们认为的事件分发 之前的流程,也就是说事件是怎么来的?一路又是怎么走下去的?

本文的整体过程在我的这篇面经《 经过20天的面试终于进了阿里》 中的天眼查环节有被提及,感兴趣的可以点进去看看。

那为什么要研究事件的来龙去脉呢?其实我写本文之前是知道事件是以下的这种走向传递的。

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

直到有一天,有人问我那Activity是怎么接到事件的?我当时是一脸懵逼,然后就没有然后了,所以至于是谁问的我,估计你们也知道了。

本文争取以极简的方式来描述,如有问题欢迎指正。


我们开始表演 首先整个过程中会涉及以下几个类

  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,我们先上结论,后展开说明,以下部分纯干货,如之前看过部分源码可直接看这部分,对源码不是很熟悉的也没有关系,下一个环节就是展开说明。

先上张图理解一下,图片内容较多,可点击原图欣赏无码大图 。 事件来源示例图.png

    1. ActivityThread.performLaunchActivity() 中调用 Activity.attach(),创建PhoneWindow,PhoneWindow创建DecorView
    1. ActivityThread.handleResumeActivity()中调用Activity.makeVisible(),初始化WindowManager并调用WindowManager.addView()
    1. 因为WindowManager是接口,找到其实现类并调用WindowManagerImpl.addView()
    1. 调用WindowManagerGlobal.addView(View view, ..., Window parentWindow, ...)

此时需要注意view为DecorView对象,该对象是通过**ActivityThread.handleResumeActivity()中的PhoneWindow.getDecorView()**获取,并直接赋值给Activity变量的。

    1. WindowManagerGlobal.addView()创建了ViewRootImpl,并调用了ViewRootImpl.setView(view,...,...)
    1. 连带创建InputChannel,InputQueue以及WindowInputEventReceiver对象并传入InputChannel和Looper
    1. 其实Android事件的源头来自于用户输入行为,由硬件进行捕获,一般会保存在dev/input节点下,后续组装成KeyEvent/MotionEvent对象,经Native进入Java的**InputEventReceiver.dispatchInputEvent()**中。
    1. ViewRootImpl.WindowInputEventReceiver extends InputEventReceiver,连带调用了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q),方法中获取到mFirstPostImeInputStage对象其实为ViewPostImeInputStage
    1. ViewPostImeInputStage extends InputStage 因此执行onProcess(),判断如果是触摸事件,调用processPointerEvent(),然后内部调用mView.dispatchPointerEvent(),此时的mView为DecorView,辗转调用到了DecorView.dispatchTouchEvent()
    1. 通过 mWindow.getCallback()获取Window.Callback然后调用Window.Callback.dispatchTouchEvent(),这个Callback就是PhoneWindow里的mCallback,而mCallback则是**Activity的attach()**赋值的,此处也就自然调用到了Activity中,后续就是我们都知道的事件分发了,一个完整的闭环就结束了。

  1. ActivityThread.performLaunchActivity()
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          ...
            if (activity != null) {
               ...
              //可以看到此处调用了Activity的attach()方法
                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);
            ...
            }
}
复制代码
  • 1.1 Activity.attach()中进行了PhoneWindow初始化,并设置CallBack对象。
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初始化
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
复制代码

2.ActivityThread.handleResumeActivity() 通过PhoneWindow获取DecorView,然后调用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();
            }
        }
 ...
    }
复制代码
  • 2.1 PhoneWindow创建DecorView的过程
   @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            //组装DecorView
            installDecor();
        }
        return mDecor;
    }

   private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //获取DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
    }
      //返回值就是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());
    }
复制代码
  • 2.2. activity.makeVisible()过程
  void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            //此时调用的其实事WindowManager.addView()
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
复制代码
  1. WindowManager是接口,找到其实现类并调用WindowManagerImpl.addView()
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
       //此时调用的是WindowManagerGlobal.addView()
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
复制代码
  1. WindowManagerGlobal.addView() 初始化ViewRootImpl,然后调用root.setView()
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
      ...
         synchronized (mLock) {
           ...
           //初始化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);
                }
                throw e;
            }
        }
    }
复制代码
  1. ViewRootImpl.setView(view,...,...) 创建InputChannel, InputQueue, 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) {
                        //创建对象
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
              }
            
        }
    }
复制代码
  1. 其实Android事件的源头来自于用户输入行为,由硬件进行捕获,一般会保存在 dev/input 节点下,后续组装成KeyEvent/MotionEvent对象,经Native进入Java的**InputEventReceiver.dispatchInputEvent()**中。

我们先分析InputEventReceiver

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

public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }
复制代码

由于InputEventReceiver是abstract类,因此需要找到对应的实现类,此时第5步中 WindowInputEventReceiver 即是实现类,因此找到重写的onInputEvent()方法进行下一步分析。

  1. ViewRootImpl.WindowInputEventReceiver extends InputEventReceiver,连带调用了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q),方法中获取到mFirstPostImeInputStage对象其实为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() {
        // 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 {
                //此时返回两个InputStage类型的对象,这个InputStage为abstract因此需要找对应的实现类。
                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);
        }
    }
复制代码

此时需要注意这个InputStage 的实现类相对比较多,不过可以重点看下setView()方法的末尾,着重看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();
                //最终new了ViewPostImeInputStage
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                //继续看viewPostImeStage赋值
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                //初始化并传入nativePostImeStage,需要查看nativePostImeStage赋值逻辑,继续往上看。
                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);
                    }
                }
}
复制代码

通过以上逻辑可以清晰的看到mFirstPostImeInputStage 即是ViewPostImeInputStage,我们需要跟进ViewPostImeInputStage看下对应的**onProcess()**方法。

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 {
                    return processGenericMotionEvent(q);
                }
            }
        }
...
}
复制代码

以上逻辑中先判断了输入事件是键盘,还是触摸,触摸逻辑中又判断了输入源是屏幕还是trackball(我猜测这判断的是鼠标等设备输入),不过不重要,我们主要看屏幕输入的逻辑,也就是**processPointerEvent(q);**方法

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

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            //主要看这个地方,mView即是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;
        }
复制代码

到此我需要简单做个总结,来捋一下这个流程,方便大家理解。 其实以上的mView即是DecorView。 该对象是在最开始通过PhoneWindow创建。 由WindowManager.addView(View v...)传递到WindowManagerGlobal.addView(View v..)。 然后通过ViewRootImpl.setView(View v...)传到了ViewRootImpl因此ViewRootImpl的变量mView即是DecorView,所以我们需要跟进DecorView.dispatchPointerEvent() 由于DecorView extends View,因此先看View.dispatchPointerEvent()

 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            //调用dispatchTouchEvent(),而DecorView重写了dispatchTouchEvent()
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
复制代码

跟进DecorView.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
复制代码

这里需要注意下这个Window.Callback cb,是通过mWindow.getCallback()获取的。 mWindow是哪来的? 这个mWindow是一个PhoneWindow对象。 是在ActivityThread.handleResumeActivity()时,通过Activity.window.getDecorView()调用PhoneWindow.installDecor() 时 **mDecor.setWindow(this);赋值的。 因此需要查看PhoneWindow.getCallback()**是哪里赋值的。 在ActivityThread.prefromLuncherActivity()中,调用的Activity.attach(),此方法中对PhoneWindow的Window.Callback进行了赋值,传入的是自己。

 mWindow.setCallback(this);
复制代码

因此,Window.Callback cb即是Activity,找到Activity.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
复制代码

此时事件怎么到达的Activity已经完全描述清楚,后续的流程我们就都知道了,不过我可以简单描述下。

以上方法中调用了getWindow().superDispatchTouchEvent(ev)

  • 即是调用的PhoneWindow. superDispatchTouchEvent(ev)

PhoneWindow调用mDecor.superDispatchTouchEvent(event);

  • 即是调用DecorView..superDispatchTouchEvent(event);

DecorView调用super.dispatchTouchEvent(event);

  • 因为DecorView extends FrameLayout,FrameLayout extends ViewGroup,因此调用ViewGroup.dispatchTouchEvent()

通过连带调用ViewGroup.dispatchTransformedTouchEvent() 调用super.dispatchTouchEvent(event)

  • 因为ViewGroup extends View, 因此会调用View.dispatchTouchEvent()事件又传递到了View,整个流程也就完整。至于时间拦截,异常处理等细节本篇将不在赘述,如有问题欢迎指正。

如果本篇内容对你有一点点帮助的话,麻烦帮忙点个👍🏻

文章分类
Android
文章标签