WindowManager添加View的事件传递机制与差异分析

147 阅读7分钟

直接用WindowManager添加的View(以下简称"WM View")的事件传递路径与Activity中的View确实存在显著差异,这主要是由于它们所处的窗口层级结构和依附关系不同所导致的。下面将从源码层面为你分析。

WindowManager添加View的事件传递机制与差异分析

1️⃣ 核心差异概览

在深入源码之前,我们先通过一个表格直观对比两种方式的事件传递差异:

特性维度Activity中的ViewWindowManager直接添加的View
窗口类型应用窗口(Application Window)系统窗口/子窗口(System/Sub Window)
依附关系依附于Activity的Window直接依附于WindowManagerService
事件接收起点ViewRootImpl → DecorView → Activity直接由ViewRootImpl处理
Touch事件回调入口Activity.dispatchTouchEvent()ViewRootImpl.WindowInputEventReceiver
事件拦截与分发机制经过完整的View树层级分发(ViewGroup/View)直接由ViewRootImpl分发给目标View
默认的消费行为由View树的onTouchEvent()决定依赖View自身的onTouchEvent()或OnTouchListener
标记位(Flags)影响受Activity Window属性影响独立配置的LayoutParamsflags(如NOT_FOCUSABLE)
输入焦点处理通过Activity窗口自动管理需显式配置LayoutParamsflags
层级(Z-order)应用层级(1-99)系统层级(2000-2999)或子窗口层级(1000-1999)
WMS中的窗口状态管理通过ActivityRecord、Task等关联作为独立窗口状态管理

2️⃣ WindowManager添加View的事件传递路径

直接用WindowManager添加的View(如悬浮窗),其事件传递路径相对直接,不经过Activity的层级结构:

InputManagerService → WindowManagerService → ViewRootImpl → 目标View

下面这张序列图描绘了WM View事件的完整传递过程:

deepseek_mermaid_20250820_6cb0cc.png

2.1 核心源码解析

  1. ViewRootImpl的setView与输入通道建立
    当你调用 WindowManager.addView() 时,最终会调用到 WindowManagerGlobal.addView(),其核心工作是创建 ViewRootImpl 并调用其 setView() 方法。

    // WindowManagerGlobal.java
    public void addView(View view, ViewGroup.LayoutParams params, ...) {
        ...
        ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
        ...
        root.setView(view, wparams, panelParentView);
        ...
    }
    

    在 ViewRootImpl.setView() 中,会通过 WindowSession(一个Binder代理对象,指向WMS)将窗口添加到WMS,并建立输入事件接收的管道(InputChannel)

    // ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ...
        // 1. 添加窗口到WMS,并建立InputChannel
        res = mWindowSession.addToDisplay(..., mInputChannel);
        ...
        if (mInputChannel != null) {
            if (mInputQueueCallback != null) {
                mInputQueue = new InputQueue();
                mInputQueueCallback.onInputQueueCreated(mInputQueue);
            }
            // 2. 创建WindowInputEventReceiver,用于接收输入事件
            mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                    Looper.myLooper());
        }
    }
    

    WMS通过 openInputChannel() 方法创建一对关联的 InputChannel。其中一个在WMS端,另一个通过Binder传递回APP进程的 ViewRootImpl 端。这对 InputChannel 构成了一个全双工的管道,是APP进程与系统服务WMS之间传递输入事件的桥梁。

  2. 输入事件的接收与分发
    事件接收的核心是 WindowInputEventReceiver。当Linux内核捕捉到触摸事件,经由InputReader、InputDispatcher处理,最终WMS找到目标窗口,通过 InputChannel 将事件发送到APP进程。ViewRootImpl 的 WindowInputEventReceiver 的 onInputEvent() 方法会被调用:

    // ViewRootImpl.java - WindowInputEventReceiver
    @Override
    public void onInputEvent(InputEvent event) {
        // 将事件加入队列并进行分发
        enqueueInputEvent(event, this, 0, true);
    }
    

    enqueueInputEvent() 会最终调用 deliverInputEvent() 方法进行事件分发8。

  3. ViewRootImpl中的事件处理阶段(InputStage)
    ViewRootImpl 中有一系列 InputStage(输入事件处理阶段)构成的责任链,用于处理不同类型的事件或执行不同阶段的操作8。对于触摸事件,最重要的阶段是 ViewPostImeInputStage

    // ViewRootImpl.java
    private void deliverInputEvent(QueuedInputEvent q) {
        ...
        // 沿着InputStage责任链处理事件
        if (stage != null) {
            stage.deliver(q);
        }
        ...
    }
    

    在 ViewPostImeInputStage.processPointerEvent() 中,事件被直接分发给目标 View 的 dispatchTouchEvent() 方法:

    // ViewPostImeInputStage.java
    @Override
    protected int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        ...
        // 直接将事件分发给View
        boolean handled = mView.dispatchPointerEvent(event);
        ...
        return handled ? FINISH_HANDLED : FORWARD;
    }
    

    这里的 mView 就是你通过 WindowManager.addView() 添加的 View

  4. View对事件的处理
    事件最终到达你添加的 View 的 dispatchTouchEvent() 方法。此后的逻辑就与常规View的事件处理一致了79:

    • 如果设置了 OnTouchListener 且 View 是 ENABLED 状态,则优先执行 OnTouchListener.onTouch()。若 onTouch 返回 true,则表示事件被消费,流程结束。
    • 否则,会调用 View 自身的 onTouchEvent() 方法。

3️⃣ 与Activity中View事件传递的差异原因

为什么两者的事件传递路径不一样?核心原因在于窗口层级和依附关系的根本差异。

  1. 窗口层级与依附关系不同

    • Activity的View树:依附于Activity的 PhoneWindow 和 DecorView,是应用窗口(类型为 TYPE_APPLICATION)。它的层级在WMS中由ActivityRecord、Task等统一管理。
    • WM直接添加的View:通常被创建为系统窗口(如 TYPE_APPLICATION_OVERLAY 或 TYPE_SYSTEM_ERROR)或子窗口。它是一个独立的窗口,直接由WMS管理,与Activity的窗口平级或有自己的层级,没有父窗口或与Activity的窗口无直接关联
  2. InputChannel的归属与事件派发起点不同
    正因为窗口层级独立,WMS会为WM View单独创建InputChannel。系统服务(InputDispatcher)识别到触摸事件发生在该窗口区域后,会通过其独立的InputChannel直接将事件发送到与之关联的 ViewRootImpl,而不会经过Activity所在窗口的ViewRootImpl或DecorView8。这导致了事件传递路径的根本差异。

  3. 缺失的中间环节:Activity与PhoneWindow
    Activity的View事件传递中,DecorView 接收到事件后,会先调用 Activity.dispatchTouchEvent()

    // DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Callback cb = getCallback();
        return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                : super.dispatchTouchEvent(ev);
    }
    

    这里的 Callback 就是 Activity。因此事件会先交给 Activity 处理,之后才继续向下分发。
    WM添加的View:它没有附加到Activity的PhoneWindow和DecorView上,因此自然不会回调Activity的任何事件分发方法。事件直接从它的 ViewRootImpl 就到了它自身。

  4. 标记位(Flags)的影响
    用WindowManager添加View时,通常会配置一些特殊的LayoutParams flags,例如:

    • FLAG_NOT_FOCUSABLE:窗口不需要焦点,按键事件会直接传递给下层窗口。
    • FLAG_NOT_TOUCH_MODAL:窗口区域内的触摸事件自己处理,区域外的事件传递给下层窗口24。
      这些Flags会直接影响WMS对触摸事件的分派决策。例如,一个 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL 的悬浮窗,自身消费不掉的触摸事件(比如点在了View的空白处)会顺利传递给下层的Activity窗口;而如果设置了 FLAG_WATCH_OUTSIDE_TOUCH,则还能接收到落在窗口区域外但仍在屏幕上的触摸事件。Activity的窗口通常没有这些特殊设置,其事件处理更依赖于View树自身的逻辑。

4️⃣ 关键源码定位(基于Android SDK)

  1. WindowManagerGlobal.addView()

    • 作用:创建ViewRootImpl,建立View与ViewRootImpl的联系。
    • 位置frameworks/base/core/java/android/view/WindowManagerGlobal.java
  2. ViewRootImpl.setView()

    • 作用:通过WindowSession与WMS通信,添加窗口、建立InputChannel。
    • 位置frameworks/base/core/java/android/view/ViewRootImpl.java
  3. ViewPostImeInputStage.processPointerEvent()

    • 作用:处理非输入法相关的指针(触摸)事件,直接调用View的dispatchPointerEvent。
    • 位置frameworks/base/core/java/android/view/ViewPostImeInputStage.java (或查看ViewRootImpl中内部类)
  4. View.dispatchTouchEvent()

    • 作用:View处理事件的入口,判断OnTouchListener和onTouchEvent的调用。
    • 位置frameworks/base/core/java/android/view/View.java
  5. WindowManagerService.addWindow()

    • 作用:WMS端创建窗口对象(WindowState),处理InputChannel的注册。
    • 位置frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java (AOSP路径)

5️⃣ 总结与启示

直接用WindowManager添加的View因其独立的窗口层级直接与WMS关联的特性,导致其事件传递路径绕过了Activity的DecorView和PhoneWindow,直接由自身关联的ViewRootImpl接收并分发给View自身处理。

理解这种差异对于处理悬浮窗、系统弹窗等场景的触摸交互至关重要。例如,你需要更加注意WM View的LayoutParams配置(如flags、type),因为它们直接决定了WMS如何向你的窗口派发事件。同时,由于事件不经过Activity,一些依赖于Activity事件回调的逻辑可能需要在你自定义的View内部实现。

希望这份基于源码的分析能帮助你更透彻地理解Android的事件分发机制。