ViewRootImpl 事件分发责任链分析

0 阅读6分钟

源码路径:frameworks/base/core/java/android/view/ViewRootImpl.java

一、责任链整体结构

责任链在 setView() 方法中构建:

┌─────────────────────────────────────────────────────────────────────────────┐
│                           事件分发责任链                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  mFirstInputStage ──► NativePreImeInputStage                               │
│                              │                                              │
│                              ▼                                              │
│                      ViewPreImeInputStage                                   │
│                              │                                              │
│                              ▼                                              │
│                        ImeInputStage                                        │
│                              │                                              │
│                              ▼                                              │
│                    EarlyPostImeInputStage ◄── mFirstPostImeInputStage      │
│                              │                                              │
│                              ▼                                              │
│                    NativePostImeInputStage                                  │
│                              │                                              │
│                              ▼                                              │
│                     ViewPostImeInputStage                                   │
│                              │                                              │
│                              ▼                                              │
│                      SyntheticInputStage (终端)                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

二、InputStage 基类设计

核心机制 (ViewRootImpl.java):

abstract class InputStage {
    private final InputStage mNext;  // 下一个处理节点

    // 三种返回结果
    protected static final int FORWARD = 0;           // 转发给下一阶段
    protected static final int FINISH_HANDLED = 1;    // 已处理,结束分发
    protected static final int FINISH_NOT_HANDLED = 2;// 未处理,结束分发

    public final void deliver(QueuedInputEvent q) {
        if (已结束) {
            forward(q);
        } else if (应丢弃) {
            finish(q, false);
        } else {
            int result = onProcess(q);  // 子类实现
            apply(q, result);
        }
    }
}

AsyncInputStage 扩展 (ViewRootImpl.java):

  • 增加 DEFER 状态:支持异步/延迟处理
  • 用于 IME、Native 等需要等待回调的场景

三、各 Stage 职责详解

责任链以 ImeInputStage 为分界线,分为「IME 前处理」「IME 处理」「IME 后处理」三个阶段:

IME 前处理阶段

NativePreImeInputStage

  • Native 层处理 KEYCODE_BACK 的 OnBackInvokedCallback 机制
  • 支持跳过 IME 的快速路径

ViewPreImeInputStage

  • 在输入法处理之前,给 View 树一个提前拦截事件的机会
  • 通常用于处理一些特殊的系统级或应用级快捷键

IME 处理阶段

ImeInputStage

  • 输入法处理阶段,这是责任链的核心分界线
  • 系统会将按键事件传递给当前连接的输入法(IME)
  • 如果 IME 消费了事件(返回 DISPATCH_HANDLED),事件分发到此结束,Activity 收不到该事件
  • 如果 IME 不处理(返回 DISPATCH_NOT_HANDLED),事件继续向下传递

IME 后处理阶段

EarlyPostImeInputStage

  • 早期处理:坐标转换、触摸模式切换、Tooltip 处理等

NativePostImeInputStage

  • Native 层处理:将事件发送给 Native Activity

ViewPostImeInputStage

  • Activity 和 View 的处理阶段
  • 只有当输入法**没有消费(拦截)**该事件时,事件才会流转到这个阶段
  • 在这个阶段,事件才会传递给 DecorView,进而调用 Activity.dispatchKeyEvent(),最后分发给具体的 View

SyntheticInputStage

  • 合成事件处理(责任链终端)
  • 将 Trackball 转换为 DPAD 按键事件、处理摇杆事件、未处理的键盘事件

四、事件入口与路由策略

deliverInputEvent() 决定入口 Stage:

private void deliverInputEvent(QueuedInputEvent q) {
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;  // 直接进入合成阶段
    } else {
        // 优化:触摸事件可跳过 IME 相关阶段
        boolean canSkipToPostIme = q.shouldSkipIme()
            && (q.mFlags & FLAG_SKIP_IME) == 0;
        stage = canSkipToPostIme
            ? mFirstPostImeInputStage  // 从 EarlyPostIme 开始
            : mFirstInputStage;        // 完整链条
    }
    stage.deliver(q);
}

五、责任链设计模式总结

特性实现方式
链式调用每个 Stage 持有 mNext 引用,通过 onDeliverToNext() 传递
处理结果FORWARD/FINISH_HANDLED/FINISH_NOT_HANDLED
异步支持AsyncInputStage 子类支持 DEFER 状态
短路优化触摸事件跳过 Pre-IME 阶段直接进入 mFirstPostImeInputStage
责任分离IME 前处理 → IME 处理 → IME 后处理 → View 分发 → 合成处理

六、输入法优先于 Activity 拿到按键事件

事件分发顺序确认

按键事件责任链(KeyEvent 流向):

┌─────────────────────────────────────────────────────────────────────────┐
│  NativePreImeInputStage                                                 │
│         │  (处理 BACK 按键、Native Activity)                             │
│         ▼                                                               │
│  ViewPreImeInputStage                                                   │
│         │  (dispatchKeyEventPreIme)                                     │
│         ▼                                                               │
│  ╔═════════════════════╗                                                │
│  ║  ImeInputStage      ║  ◄── 【IME 在这里先拿到事件】                    │
│  ╚═════════════════════╝                                                │
│         │                                                               │
│         │  IME 返回 DISPATCH_HANDLED → 事件结束,Activity 收不到          │
│         │  IME 返回 DISPATCH_NOT_HANDLED → 继续传递                       │
│         ▼                                                               │
│  EarlyPostImeInputStage                                                 │
│         │  (触摸模式切换、坐标转换)                                        │
│         ▼                                                               │
│  NativePostImeInputStage                                                │
│         │  (Native Activity Post-IME)                                   │
│         ▼                                                               │
│  ╔═════════════════════════╗                                            │
│  ║  ViewPostImeInputStage  ║  ◄── 【Activity/View 在这里收到事件】         │
│  ╚═════════════════════════╝                                            │
│         │  mView.dispatchKeyEvent()                                     │
│         ▼                                                               │
│  SyntheticInputStage                                                    │
└─────────────────────────────────────────────────────────────────────────┘

关键代码佐证

ImeInputStage 处理逻辑

final class ImeInputStage extends AsyncInputStage {
    @Override
    protected int onProcess(QueuedInputEvent q) {
        // 事件分发给 IME
        final int result = mImeFocusController.onProcessImeInputStage(
                q, q.mEvent, mWindowAttributes, this);
        switch (result) {
            case DISPATCH_IN_PROGRESS:  // IME 正在处理,异步等待
                return DEFER;
            case DISPATCH_HANDLED:      // IME 已消费,事件结束
                return FINISH_HANDLED;
            case DISPATCH_NOT_HANDLED:  // IME 不处理,继续传递给 Activity
                return FORWARD;
        }
    }
}

七、CTS 问题场景还原

问题背景

海外 CTS 问题涉及输入法和 Activity 处理 key 事件。由于输入法在事件分发时候先于 Activity 拿到 keycode(DOWN)事件,在传递给 Activity 时附加了一个 UP 事件导致测试失败。

IME 发送按键事件流程

当 IME 通过 InputConnection.sendKeyEvent() 发送按键时:

BaseInputConnection.java:

public boolean sendKeyEvent(KeyEvent event) {
    mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
    return false;
}

InputMethodManager.java:

public void dispatchKeyEventFromInputMethod(@Nullable View targetView, @NonNull KeyEvent event) {
    // ...
    viewRootImpl.dispatchKeyFromIme(event);  // 发送给 ViewRootImpl
}

ViewRootImpl.java:

case MSG_DISPATCH_KEY_FROM_IME: {
    KeyEvent event = (KeyEvent) msg.obj;
    // 重新入队,带 FLAG_SKIP_IME 标志(跳过 IME 阶段)
    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_SKIP_IME, true);
}

问题场景

原始事件流:
  KeyEvent(DOWN) → IME 拿到 → IME 可能发送 KeyEvent(UP) 给 Activity

结果:
  Activity 收到:DOWN(原始)+ UP(来自 IME 的 sendKeyEvent)

而非预期的:
  DOWN → IME 不处理 → DOWN 传递给 Activity → UP → Activity

这可能导致 CTS 测试中事件序列不符合预期(DOWN/UP 配对异常、时序问题等)。

八、KeyEvent 与 TouchEvent 处理差异

入口路由差异

deliverInputEvent() 的关键逻辑:

InputStage stage;
if (q.shouldSendToSynthesizer()) {
    stage = mSyntheticInputStage;
} else {
    // 优化:Pointer 事件(触摸)跳过 IME 阶段
    boolean canSkipToPostIme = q.shouldSkipIme()
            && (q.mFlags & QueuedInputEvent.FLAG_SKIP_IME) == 0;
    stage = canSkipToPostIme ? mFirstPostImeInputStage : mFirstInputStage;
}

shouldSkipIme() 的判断逻辑 :

public boolean shouldSkipIme() {
    if ((mFlags & FLAG_SKIP_IME) != 0) {
        return true;  // IME 回传的事件带此标志
    }
    // MotionEvent 且来自 POINTER 源(触摸屏)则跳过 IME
    return mEvent instanceof MotionEvent
            && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER));
}

事件流向对比

┌─────────────────────────────────────────────────────────────────────────────┐
│                        KeyEvent 流向(完整责任链)                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  NativePreImeInputStage ──► ViewPreImeInputStage ──► ImeInputStage          │
│         │                        │                     │                    │
│         ▼                        ▼                     ▼                    │
│  (处理 BACK/ Native)      (dispatchKeyEventPreIme)  (IME 处理)              │
│                                                                             │
│                              ▼                                              │
│                    EarlyPostImeInputStage                                   │
│                              │                                              │
│                              ▼                                              │
│                    NativePostImeInputStage                                  │
│                              │                                              │
│                              ▼                                              │
│                    ViewPostImeInputStage                                    │
│                              │                                              │
│                              ▼                                              │
│                    SyntheticInputStage                                      │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                    TouchEvent 流向(优化路径,跳过 IME)                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  NativePreImeInputStage ──✕── ViewPreImeInputStage ──✕── ImeInputStage     │
│         (跳过)                  (跳过)                (跳过)                │
│                                                                             │
│  直接入口 ▼                                                                 │
│                    EarlyPostImeInputStage ◄── mFirstPostImeInputStage      │
│                              │                                              │
│                              ▼                                              │
│                    NativePostImeInputStage                                  │
│                              │                                              │
│                              ▼                                              │
│                    ViewPostImeInputStage                                    │
│                              │                                              │
│                              ▼                                              │
│                    SyntheticInputStage                                      │
└─────────────────────────────────────────────────────────────────────────────┘

各 Stage 对事件类型的处理

StageKeyEventTouchEvent处理说明
NativePreImeInputStage✗ FORWARD只处理 KeyEvent (BACK、Native Activity)
ViewPreImeInputStage✗ FORWARD只处理 KeyEvent,注释明确 "Does not support pointer events"
ImeInputStage注释明确 "Does not support pointer events"
EarlyPostImeInputStage两种都处理:KeyEvent 做触摸模式切换;TouchEvent 做坐标转换
NativePostImeInputStage通过 InputQueue 发送给 Native Activity
ViewPostImeInputStage核心分发:dispatchKeyEvent() / dispatchPointerEvent()
SyntheticInputStage合成处理:Trackball→DPAD、Joystick、未处理的 KeyEvent

设计原因

为什么 TouchEvent 要跳过 IME 阶段?

  1. IME 只关心按键事件:输入法需要处理的是键盘输入,用于文字输入,触摸事件对 IME 无意义

  2. 性能优化:触摸事件频率极高(ACTION_MOVE 连续触发),跳过不必要的阶段减少开销

  3. 逻辑正确性:如果触摸事件经过 IME,可能导致 IME 的 UI 交互与 App 的触摸事件产生冲突

特殊场景:IME 回传事件

当 IME 通过 sendKeyEvent() 回传按键事件时:

// ViewRootImpl.java
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_SKIP_IME, true);

带有 FLAG_SKIP_IME 标志,确保回传的事件不会再经过 ImeInputStage,避免死循环。


九、从 ViewPostImeInputStage 到 Activity/View 的完整传递链

KeyEvent 传递完整流程

ViewPostImeInputStage.onProcess()
        │
        │  mView.dispatchKeyEvent(event)
        │  (mView = DecorView,根 View)
        ▼
DecorView.dispatchKeyEvent()  (DecorView.java)
        │
        │  Window.Callback cb = mWindow.getCallback()
        │  cb.dispatchKeyEvent(event)  (cb = Activity)
        ▼
Activity.dispatchKeyEvent()  (Activity.java)
        │
        │  win.superDispatchKeyEvent(event)
        │  (win = PhoneWindow)
        ▼
PhoneWindow.superDispatchKeyEvent()  (PhoneWindow.java)
        │
        │  mDecor.superDispatchKeyEvent(event)
        ▼
DecorView.superDispatchKeyEvent()  (DecorView.java)
        │
        │  super.dispatchKeyEvent(event)
        │  (DecorView 继承自 FrameLayout)
        ▼
ViewGroup.dispatchKeyEvent()
        │
        │  分发给子 View
        ▼
View.dispatchKeyEvent()

关键代码佐证

1. DecorView.dispatchKeyEvent() (DecorView.java):

final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0
        ? cb.dispatchKeyEvent(event)  // 分发给 Activity
        : super.dispatchKeyEvent(event);

2. Activity.dispatchKeyEvent() (Activity.java):

public boolean dispatchKeyEvent(KeyEvent event) {
    onUserInteraction();
    // ...
    Window win = getWindow();
    if (win.superDispatchKeyEvent(event)) {  // 分发给 PhoneWindow
        return true;
    }
    // 如果 View 树没有消费,交给 KeyEvent.dispatch() 处理
    return event.dispatch(this, decor.getKeyDispatcherState(), this);
}

3. PhoneWindow.superDispatchKeyEvent() (PhoneWindow.java):

public boolean superDispatchKeyEvent(KeyEvent event) {
    return mDecor.superDispatchKeyEvent(event);  // 回到 DecorView
}

4. DecorView.superDispatchKeyEvent() (DecorView.java):

public boolean superDispatchKeyEvent(KeyEvent event) {
    if (mActionModeController.dispatchKeyEvent(event)) {
        return true;
    }
    return super.dispatchKeyEvent(event);  // 调用父类 ViewGroup 的方法
}

设计目的:Activity 与 View 的解耦

步骤目的
DecorView → Activity让 Activity 有机会拦截/预处理按键(如菜单键、返回键)
Activity → PhoneWindowPhoneWindow 可以处理窗口级别的逻辑
PhoneWindow → DecorView → ViewGroup最终分发给 View 树

这个设计精妙地实现了组件(Activity)与视图(View)的解耦

  • 事件虽然由底层的 View 树接收
  • 但业务逻辑的控制中心(Activity)始终拥有第一优先级的处理权

Activity 可以在事件到达 View 树之前拦截处理,比如:

  • ActionBar 处理菜单键
  • 处理返回键逻辑
  • 分发快捷键事件

参考资料

  • 源码:frameworks/base/core/java/android/view/ViewRootImpl.java
  • 相关类:
    • InputMethodManager.java
    • BaseInputConnection.java
    • ImeFocusController.java