源码路径:
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 对事件类型的处理
| Stage | KeyEvent | TouchEvent | 处理说明 |
|---|---|---|---|
| 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 阶段?
-
IME 只关心按键事件:输入法需要处理的是键盘输入,用于文字输入,触摸事件对 IME 无意义
-
性能优化:触摸事件频率极高(ACTION_MOVE 连续触发),跳过不必要的阶段减少开销
-
逻辑正确性:如果触摸事件经过 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 → PhoneWindow | PhoneWindow 可以处理窗口级别的逻辑 |
| PhoneWindow → DecorView → ViewGroup | 最终分发给 View 树 |
这个设计精妙地实现了组件(Activity)与视图(View)的解耦。
- 事件虽然由底层的 View 树接收
- 但业务逻辑的控制中心(Activity)始终拥有第一优先级的处理权
Activity 可以在事件到达 View 树之前拦截处理,比如:
- ActionBar 处理菜单键
- 处理返回键逻辑
- 分发快捷键事件
参考资料
- 源码:
frameworks/base/core/java/android/view/ViewRootImpl.java - 相关类:
InputMethodManager.javaBaseInputConnection.javaImeFocusController.java