收藏文章 楼主

Android 事件分发溯源详解 | 开发者说·DTalk

版块:软件应用   类型:普通   作者:小羊羔links   查看:626   回复:0   获赞:0   时间:2022-01-23 19:16:45

本文原作者: BennuC,原文发布于: BennuCTech。


Android 事件分发机制大家都非常熟悉,大部分文章对这个过程的描述都是开始于 Activity,但是事件是怎么传到 Activity 的?


这里就涉及到几个重要的部分: Window,WMS,ViewRoot 和 DecorView。


如果要理解事件分发的源头,就需要搞明白他们之间的关系,所以我们先来看看它们到底有什么关系?



Window



Window 是我们比较熟悉的,那么它是如何创建的?


我们来看 Activity 的 attach 函数:
@UnsupportedAppUsagefinal 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);    ...
mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(mWindowControllerCallback); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); ...
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ... mWindowManager = mWindow.getWindowManager(); ...}

我这里只展示一部分关键代码。当我们的 activity 创建完成后会执行 attach,这时可以看到创建了 PhoneWindow,同时也设置了 WindowManager。


注意 mWindow.setCallback(this); 这行代码,Activity 本身实现了 Window.Callback 接口:

public class Activity extends ContextThemeWrapper        implements LayoutInflater.Factory2,        Window.Callback, ... {


这里将 activity 赋值给 window 的 callback,在后面的流程中会发挥作用。



DecorView



DecorView 是整个布局的最顶端的 view,也就是根布局。它很容易与 ViewRoot 搞混,ViewRoot 其实不是 View,后面再来说它。


DecorView 是如何创建的,这一切要从 setContentView 说起:
public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}


可以看到执行了 window 的 setContentView,它的源码:
    @Override    public void setContentView(int layoutResID) {        if (mContentParent == null) {            installDecor();        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            mContentParent.removeAllViews();        }
... }


可以看到一开始就执行 installDecor,这里就是初始化 DecorView:
   private void installDecor() {        mForceDecorInstall = false;        if (mDecor == null) {            mDecor = generateDecor(-1);            ...        } else {            mDecor.setWindow(this);        }        if (mContentParent == null) {            mContentParent = generateLayout(mDecor);
... } }

可以看到先通过 generateDecor 创建了 DecorView:
    protected DecorView generateDecor(int featureId) {        Context context;        ...        return new DecorView(context, featureId, this, getAttributes());    }


创建时将 Window 也传入了,所以 DecorView 中保存了一份 Window 的引用。


然后回到 installDecor 代码中,又执行了 generateLayout,这里创建了 mContentParent:

    protected ViewGroup generateLayout(DecorView decor) {        ...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ...
return contentParent; }

可以看到这个 mContentParent 就是 ID_ANDROID_CONTENT,所以它才是真正装载我们通过 setContentView 所设置的布局那个 ViewGroup。所以这个层级应该是:

DecorView -> mContentParent -> 实际布局



ViewRoot



通过上面可以看出,Window 的创建是在 attach 环节,而 DecorView 则是在 create 环节。目前虽然创建了 DecorView,但是还没有真正添加到 Window 中,而且 ViewRoot 还没有创建出来,这两步实际上是一起的,下面来看一下。

Activity 创建完成后会通知 AMS,AMS 处理一些事务后会通知 Activity 显示,这样就会执行 ActivityThreadhandleResumeActivity():
    @Override    public void handleResumeActivity(ActivityClientRecord r, 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; ... if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { a.onWindowAttributesChanged(l); } } } else if (!willBeVisible) { ... }
... }


这里会通过 WindowManageraddView 函数将 DecorView 添加到屏幕上。WindowManager 的实现类是 WindowManagerImpl,它的函数代码如下:
    @Override    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyTokens(params);        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,                mContext.getUserId());    }

实际上是执行了 WindowManagerGlobaladdView:
    public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow, int userId) {        ...
ViewRootImpl root; View panelParentView = null;
synchronized (mLock) { ...
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) { ... } } }


这里我们看到创建了 ViewRootImpl,这就是 ViewRoot。然后将 DecorView 也传入了,这样 ViewRoot 就持有了 DecorView。

那么 ViewRoot 到底是什么?

我们可以看成是管理 DecorView 的一个类,比如它初始化的时候得到了一个 WindowSession:
    public ViewRootImpl(Context context, Display display) {        this(context, display, WindowManagerGlobal.getWindowSession(),                false /* useSfChoreographer */);    }

通过 WindowSession 可以与 WMS 进行通信实现一些窗口信息的传递。


另外在它的 setView 中还创建了一个 WindowInputEventReceiver:
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,            int userId) {        synchronized (this) {            if (mView == null) {                ...                InputChannel inputChannel = null;                if ((mWindowAttributes.inputFeatures                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {                    inputChannel = new InputChannel();                }                ...                try {                    ...                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(), userId,                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,                            mTempControls);                    ...                } catch (RemoteException e) {                    ...                } finally {                    ...                }                ...                if (inputChannel != null) {                    ...                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,                            Looper.myLooper());
... } } } }


这个就是用来接收事件的,下面我们来顺着这个看看事件是如何分发到 view 的。



事件源头



上面创建 WindowInputEventReceiver 时,可以看到传入了一个 InputChannel,它创建之后又传入了 WindowSession,即 WMS。InputChannel 就是底层通知上层事件的核心,系统服务捕获到屏幕事件后,会通过它通知到上层,也就是 WindowInputEventReceiver

所以 WindowInputEventReceiver 是整个事件的源头:
    final class WindowInputEventReceiver extends InputEventReceiver {        ...
@Override public void onInputEvent(InputEvent event) { ... 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); } }
... }


事件进入它的 onInputEvent 后会执行 enqueueInputEvent:

    void enqueueInputEvent(InputEvent event,            InputEventReceiver receiver, int flags, boolean processImmediately) {        ...        if (processImmediately) {            doProcessInputEvents();        } else {            scheduleProcessInputEvents();        }    }


这里通过判断立即执行还是延迟处理,结果差不多,来看立即执行的代码:
    void doProcessInputEvents() {        // Deliver all pending input events in the queue.        while (mPendingInputEventHead != null) {            ...            deliverInputEvent(q);        }
... }


进入 deliverInputEvent:
    private void deliverInputEvent(QueuedInputEvent q) {        ...        try {            ...
InputStage stage; if (q.shouldSendToSynthesizer()) { stage = mSyntheticInputStage; } else { stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; }
...
if (stage != null) { handleWindowFocusChanged(); stage.deliver(q); } else { finishInputEvent(q); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }


可以看到通过 stage 进行了 deliver,这个 stage 是什么?

setView 的最后有这么一段代码:
// Set up the input pipeline.                CharSequence counterSuffix = attrs.getTitle();                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; mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;


可以看到是一个套一个,我们重点来看 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 { return processGenericMotionEvent(q); } } }
...
private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; boolean handled = mView.dispatchPointerEvent(event); ... return handled ? FINISH_HANDLED : FORWARD; }
... }


InputStage 的 deliver 最终会通过 onProcess 来区分事件处理 (这块就不细说了,没意义),其中我们最关心的事件交给了 processPointerEvent 来处理。在这里可以看到执行了 mView.dispatchPointerEvent(event),这个 View 是我们提到的 DecorView。这样我们总算找到了源头,下面看看是怎么传递下去的。



事件传递



dispatchPointerEvent 这个函数是 View 的一个函数,源码:
public final boolean dispatchPointerEvent(MotionEvent event) {    if (event.isTouchEvent()) {        return dispatchTouchEvent(event);    } else {        return dispatchGenericMotionEvent(event);    }}


到了我们熟悉的 dispatchTouchEvent 了,这样直接就将事件分发到各个 View 了?并不是,来看看 DecorView 中这块是如何处理的:
@Overridepublic 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,然后执行了 callback 的 dispatchTouchEvent


不知道大家是否还记得我们一开始分析 Window 的创建的时候提到过 (不记得可以回到文章开始看一下),Activity 本身实现了 Window.Callback 接口,并设置给了 window 的 callback。所以这里的 callback 其实就是 Activity,这样事件就传递到了 Activity:
public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}


Activity 中之间将事件传递给了 Window,调用了它的 superDispatchTouchEvent 函数,实际上是 PhoneWindow 的实现:
    @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);    }

这样就又传递回 DecorView 了:
public boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}


在 DecorView 中执行了 super.dispatchTouchEvent(event);,它的父类就是 ViewGroup,这样就进入了我们熟悉的 ViewGroup 分发事件的环节了。



setCallBack



通过上面的分析,我们彻底理解了事件是怎么传递到 Activity,然后又如何分发到 View 上的。

但是这里有一个需要注意的点,就是 Window 的 setCallBack 函数是对外的,我们可以设置一个自定义的 CallBack,但是这样导致 Activity 这个 CallBack 被挤掉,结果就是 Activity 无法接收到事件。

那么事件还能不能分发下去了呢?我们来看看:
getWindow().setCallback(new Window.Callback() {    ...
@Override public boolean dispatchTouchEvent(MotionEvent event) { return false; }
...});


这里不论我们返回 true 还是 false,事件都不会分发下去。根据上面分析我们知道,在 Activity 中是调用了 getWindow().superDispatchTouchEvent(event); 才让事件继续分发的。所以这里我们也可以加上这样的代码,当然最好是调用 Activity 的 dispatchTouchEvent,保证流程的完整。



总结



经过上面的分析,我们知道事件传递路径大致是:
ViewRoot -> DecorView -> Activity(Window.CallBack) -> Window -> DecorView -> ViewGroup -> ...

后面就是我们熟悉的事件分发流程。






查看更多开发者精彩分享




"开发者说·DTalk" 面向

中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察 见解、移动开发过程中的心得 新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。




 点击屏末 |   |  报名参与 "开发者说·DTalk" 




小羊羔锚文本外链网站长https://seo-links.cn 
回复列表
默认   热门   正序   倒序

回复:Android 事件分发溯源详解 | 开发者说·DTalk

Powered by 小羊羔外链网 8.3.7

©2015 - 2024 小羊羔外链网

免费发软文外链 鄂ICP备16014738号-6

您的IP:18.232.169.110,2024-03-28 21:29:34,Processed in 0.04971 second(s).

支持原创软件,抵制盗版,共创美好明天!
头像

用户名:

粉丝数:

签名:

资料 关注 好友 消息