浅析:WindowManager添加的 View 的事件传递机制

要理解直接通过WindowManager添加的 View 的事件传递机制,以及它与 Activity 中 View 的差异,我们需要从事件传递的起点Window 与 ViewRootImpl 的关联根 View 的事件分发逻辑三个层面结合源码分析。

一、事件传递的总入口:InputManagerService 与 ViewRootImpl

无论哪种 View(Activity 中的 View 或 WindowManager 添加的 View),触摸事件的源头都是InputManagerService(IMS) 。IMS 通过系统底层捕获触摸事件后,会根据事件坐标和 Window 的 Z-order(层级)确定目标 Window,然后通过该 Window 对应的InputChannel 将事件传递给其关联的ViewRootImpl

ViewRootImpl是事件传递到 View 树的第一个 Java 层节点,其核心处理逻辑在dispatchInputEvent(InputEvent event)方法中:

java 复制代码
// ViewRootImpl.java
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
    // 1. 创建事件处理的Runnable
    InputStage stage = mFirstInputStage;
    if (stage != null) {
        stage.deliver(event, receiver); // 事件进入处理流水线
    } else {
        // 若无处理阶段,直接finish
        finishInputEvent(event, receiver, true);
    }
}

InputStage是事件处理的流水线,最终会调用到ViewPostImeInputStageonProcess()方法,在这里完成从ViewRootImpl到根 View 的事件传递:

java 复制代码
// ViewRootImpl$ViewPostImeInputStage.java
protected int onProcess(InputEvent event) {
    if (event instanceof MotionEvent) {
        // 核心:将触摸事件分发给根View(mView)
        return processPointerEvent((MotionEvent) event);
    }
    // ...其他事件类型
}

private int processPointerEvent(MotionEvent event) {
    // mView是当前Window的根View(如Activity的DecorView,或WindowManager添加的View)
    if (mView.dispatchPointerEvent(event)) { 
        return FINISH_HANDLED; // 事件被消费
    }
    return FORWARD; // 未被消费,继续传递
}

可见:所有 Window 的事件都会先经过ViewRootImpl,再传递给其管理的根 View(mView) 。这是两种场景的共同点。

二、Activity 中 View 的事件传递路径:多了 Activity 的 "中转"

Activity 中的 View(如布局中的 Button)的根 View 是DecorView,而DecorView与 Activity 存在强关联(Activity 是PhoneWindowCallback),这导致事件传递多了一层 "Activity 中转"。

具体路径拆解(结合源码):

  1. ViewRootImpl → DecorViewViewRootImplmViewDecorView,调用DecorView.dispatchPointerEvent(event),最终会触发DecorView.dispatchTouchEvent(event)

  2. DecorView → ActivityDecorViewdispatchTouchEvent会优先将事件交给其关联的Callback(即 Activity):

    java 复制代码
    // DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback(); 
        // mWindow是PhoneWindow,其Callback是Activity
        return cb != null && !mWindow.isDestroyed() 
            ? cb.dispatchTouchEvent(ev) // 调用Activity的dispatchTouchEvent
            : super.dispatchTouchEvent(ev);
    }
  3. Activity → PhoneWindow → DecorView :Activity 的dispatchTouchEvent会将事件回传给PhoneWindow,最终回到DecorView

    java 复制代码
    // Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction(); // 空实现,供子类重写
        }
        // getWindow()是PhoneWindow,调用其superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true; // 事件被消费
        }
        return onTouchEvent(ev); // 若所有View都不消费,Activity自己处理
    }
    
    // PhoneWindow.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        // 最终调用DecorView的super.dispatchTouchEvent(即ViewGroup的实现)
        return mDecor.superDispatchTouchEvent(event); 
    }
  4. DecorView → View 树 :回到DecorView后,事件按照ViewGroup → View的正常传递规则(dispatchTouchEventonInterceptTouchEventonTouchEvent)向下分发。

三、WindowManager 添加的 View 的事件传递路径:无 Activity 中转

通过WindowManager.addView(view, params)添加的 View(如悬浮窗、Dialog 的 ContentView),其事件传递路径更 "直接",核心原因是:这类 View 所属的 Window 没有关联 Activity 作为Callback,且根 View 也不是DecorView(或虽为 DecorView 但无 Activity 回调)

具体路径拆解:

  1. ViewRootImpl → 根 View :这类 View 会被作为 "根 View" 关联到一个新的ViewRootImpl(每个 Window 对应一个ViewRootImpl)。ViewRootImplmView就是我们添加的 View(或其包装的根 View,如 Dialog 的DecorView但无 Activity 回调)。

  2. 根 View → View 树 :由于根 View 没有关联 Activity 作为Callback,其dispatchTouchEvent会直接走ViewGroup/View的默认实现,不会经过 Activity 中转:

    • 若添加的是普通View(如TextView),则直接调用其dispatchTouchEvent
    • 若添加的是DialogDecorView,其mWindowCallbackDialog自身(而非 Activity),因此事件会先交给Dialog.dispatchTouchEvent,再回传给DecorView继续分发(但始终不会涉及 Activity)。

四、核心差异原因总结

  1. Window 的 Callback 不同

    • Activity 的 Window(PhoneWindow)的Callback是 Activity,因此DecorView会优先将事件交给 Activity 处理(形成 "中转")。
    • WindowManager 添加的 View 所属的 Window(如PopupWindow的 Window、自定义 Window)的Callback是自身(如PopupWindow)或null,不会关联 Activity,因此事件无需经过 Activity。
  2. 根 View 的分发逻辑不同

    • Activity 的根 View 是DecorView,其dispatchTouchEvent被重写,强制优先调用Window.Callback(即 Activity)。
    • WindowManager 添加的 View 的根 View(如普通ViewGroupDialogDecorView)的dispatchTouchEvent遵循ViewGroup/View的默认逻辑,无 Activity 相关的分发步骤。
  3. Window 的归属不同

    • Activity 的 Window 是 "应用主窗口",与 Activity 生命周期强绑定,事件传递需要 Activity 参与(如onUserInteraction等生命周期回调)。
    • WindowManager 添加的 View 通常属于 "子窗口" 或 "系统窗口",独立于 Activity 生命周期,事件传递无需依赖 Activity。

总结

  • Activity 中 View 的事件路径ViewRootImpl → DecorView → Activity → PhoneWindow → DecorView → View树(因 Activity 是 Window 的 Callback,多了中转)。

  • WindowManager 添加的 View 的事件路径ViewRootImpl → 根View → View树(无 Activity 作为 Callback,直接分发)。

差异的本质是Window 的 Callback 是否为 Activity,以及根 View 是否有针对 Activity 的事件转发逻辑。

相关推荐
zhangphil22 分钟前
Android Coil 3拦截器Interceptor计算单次请求耗时,Kotlin
android·kotlin
DokiDoki之父1 小时前
多线程—飞机大战排行榜功能(2.0版本)
android·java·开发语言
用户2018792831673 小时前
强制关闭生命周期延时的Activity实现思路
android
用户2018792831673 小时前
Activity后生命周期暂停问题
android
顾林海3 小时前
从"面条代码"到"精装别墅":Android MVPS架构的逆袭之路
android·面试·架构
白夜4 小时前
Android 开发工程常识
android
编程乐学4 小时前
原创模板--基于 Android 开发的驾考训练App
android·android studio·大作业·移动端开发·安卓移动开发·驾考宝典·驾考app
阿赵3D4 小时前
Unity2022打包安卓报错的奇葩问题
android·unity·安卓