浅析: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 的事件转发逻辑。

相关推荐
sky0Lan7 分钟前
一个类似 pytest 的 html 报告
android·html·pytest
怪兽201431 分钟前
Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?
android·面试
雨白1 小时前
掌握协程的边界与环境:CoroutineScope 与 CoroutineContext
android·kotlin
木易 士心2 小时前
Android 开发核心知识体系与面试指南精简版
android·面试·职场和发展
一棵树73512 小时前
Android OpenGL ES初窥
android·大数据·elasticsearch
初级代码游戏2 小时前
MAUI劝退:安卓实体机测试
android
奔跑中的蜗牛6663 小时前
直播APP跨平台架构实践(二):KMP UI 与 Rust 下载引擎协作实践
android
沐怡旸3 小时前
【底层机制】【Android】AIDL原理与实现机制详解
android·面试
小仙女喂得猪3 小时前
2025 跨平台方案KMP,Flutter,RN之间的一些对比
android·前端·kotlin
2501_915106324 小时前
iOS 混淆与 IPA 加固全流程,多工具组合实现无源码混淆、源码防护与可审计流水线(iOS 混淆|IPA 加固|无源码加固|App 防反编译)
android·ios·小程序·https·uni-app·iphone·webview