前言
Android 的输入事件分发是一套精心设计的管道体系。当手指触碰到屏幕,从硬件中断到 Activity.dispatchTouchEvent() 被调用,中间经历了一系列层次分明的调度。理解这条链路,是掌握 Android 事件体系的关键。
本文不堆砌源码,而是沿着事件流动的方向,讲清楚每个环节做了什么、为什么这么做。
一、概览:四段旅程
一次触摸事件从内核驱动上报到 Activity 接收到,可以分为四个阶段:
- 注册输入通道 ------ 建立管道
- 接收与入队 ------ 从底层拿到事件,放入队列
- InputStage 管线 ------ 事件在责任链中穿行
- Touch 事件派发 ------ 从 View 树根节点一路下发到 Activity
下面我们逐一展开。
markdown
ViewRootImpl ← WindowInputEventReceiver ← Native 层
│
▼ enqueueInputEvent
│
▼ InputStage Pipeline
│
▼ ViewPostImeInputStage
│
▼ View → DecorView → Activity
二、第一阶段:注册输入通道
入口在 ViewRootImpl.setView()。这个方法不仅把 DecorView 注册到 WindowManager,还做了一件关键的事:
ini
mInputEventReceiver = new WindowInputEventReceiver(
inputChannel, Looper.myLooper());
WindowInputEventReceiver 是 InputEventReceiver 的子类,它通过 InputChannel 与底层 SurfaceFlinger 建立了一个 共享内存 + 文件描述符 的通信通道。这个通道的本质是一个 socketpair,一端在 Native 层的 InputDispatcher,一端在应用进程。
关键设计:这里没有使用 Binder 传递事件,因为 Binder 的 oneway 调用仍然有事务槽限制,无法支撑高频触控事件。InputChannel 基于 socket,由 Looper 的 epoll 机制监听可读事件,效率更高。
三、第二阶段:接收与入队
事件到来时,WindowInputEventReceiver.onInputEvent() 被回调。但 WindowInputEventReceiver 不做实际处理,它立刻把事件交给 ViewRootImpl.enqueueInputEvent():
scss
onInputEvent(event)
→ enqueueInputEvent(event, receiver, flags, processImmediately)
→ doProcessInputEvents()
→ deliverInputEvent(q)
这里的 入队(enqueue) 是一个关键设计:
- 如果
processImmediately == true,事件被立即处理 - 否则事件被放到
mPendingInputEvents队列尾部,等下一帧 VSYNC 再处理
为什么要入队而不是直接处理?因为输入事件的处理是在 UI 线程,而 UI 线程可能正在执行布局或绘制。入队机制保证了事件不会冲断正在进行的 UI 工作,同时通过 VSYNC 对齐实现了输入与绘制的同步。
四、第三阶段:InputStage 管线
deliverInputEvent 把事件交给 InputStage。这是 Android 事件分发中最精妙的设计------一个 责任链模式(Chain of Responsibility) 的经典实现。
scss
InputStage.deliver(q)
→ forward(q) // 查找下一个阶段
→ onDeliverToNext(q) // 传递到下一级
→ apply(q) // 实际处理
InputStage 链的典型组成是:
arduino
→ ViewPreImeInputStage // IME 之前的预处理
→ ImeInputStage // 输入法处理
→ ViewPostImeInputStage // IME 之后的处理(最关键)
→ SyntheticInputStage // 合成事件(导航栏、菜单键等)
图中的 AsyncInputStage 是一个特殊的子类。它允许事件处理变成异步的------典型场景是 光标锚点(cursor anchor) 的等待。当 IME 需要异步获取光标位置时,AsyncInputStage 会在结果返回后再继续分发,而不会阻塞整个管线。
这种设计的巧妙之处在于:每个 Stage 只关心自己需要处理的事,不需要的调用 forward()放行。新增一种事件处理阶段时,只需在链中插入一个新 Stage,完全不需要修改其他 Stage。
五、第四阶段:Touch 事件派发
经过管线过滤后,事件到达 ViewPostImeInputStage。对于触摸事件,它会调用:
scss
ViewPostImeInputStage.onProcess(q)
→ processPointerEvent(q)
→ View.dispatchPointerEvent(q)
→ DecorView.dispatchTouchEvent(ev)
→ Window.Callback.dispatchTouchEvent(ev)
→ Activity.dispatchTouchEvent(ev)
这一路从 View 到 DecorView 到 Window.Callback 到 Activity,每一步都体现了一个关键原则:自上而下的拦截机会。
View.dispatchPointerEvent()会先询问onInterceptTouchEvent(),给上层拦截的机会DecorView作为顶层容器,处理了系统 UI 的触摸(状态栏、导航栏)Window.Callback是一个接口,Activity、Dialog、PopupWindow都实现了它,这让 Window 层与业务层解耦
到 Activity.dispatchTouchEvent() 之后,事件就进入了开发者最熟悉的 Activity → ViewGroup → View 的事件分发三阶段(dispatch → intercept → onTouch)。
六、架构总结
回过头来看整个流程,Android 在输入事件上的设计有几个值得借鉴的点:
6.1 分层隔离
每一层只做自己的事:
InputStage管线只负责策略(预处理、转发、异步等待)View层只负责命中测试和触摸逻辑Activity只负责业务响应
6.2 责任链模式的务实应用
InputStage 的责任链不是死板的"每个节点都处理",而是找到第一个能处理的节点。这种变体被称为"职责链-变体"(Chain of Responsibility - variant),比标准模式更高效------事件不会经过所有节点,只要被消费就停止传递。
6.3 同步与异步的透明切换
AsyncInputStage 的存在使得某些阶段可以异步处理,而其他阶段不受影响。调用方不需要关心一个 Stage 是同步还是异步执行的------这正是封装的价值。
6.4 与 VSYNC 对齐
通过入队机制和 Choreographer 的配合,输入事件的消费时机与绘制帧对齐,既避免了 UI 线程的过度忙碌,也降低了输入到显示的延迟。
七、一张图总结

这条链路上的每个设计------从 InputChannel 的选型、入队机制、责任链管线、到 Window.Callback 接口抽象------都指向同一个目标:在保证流畅度的前提下,让事件处理的每一环都能被独立理解和修改。
理解这条链路,再看 Android Framework 的其他模块(布局、绘制、按键),会发现它们遵循了相似的分层和管道思想。这不是巧合,而是 Android 架构的一贯风格。