整个流程可以概括为以下核心架构图,它展示了输入事件的数据流和核心组件:
第一阶段:内核空间 - /dev/input/eventX
- 硬件中断:当用户触摸屏幕、按下物理按键或移动鼠标时,硬件设备(触摸屏、键盘控制器等)会产生一个硬件中断(IRQ)
- 设备驱动:对应的输入设备驱动会处理这个中断,将原始的硬件信号(如触摸点的X、Y坐标、按键码、压力值等)封装成一个标准格式的数据结构(
struct input_event
)struct input_event { struct timeval time; // 时间戳 __u16 type; // 事件类型 (e.g., EV_KEY, EV_ABS, EV_SYN) __u16 code; // 事件代码 (e.g., BTN_TOUCH, ABS_X, KEY_VOLUMEDOWN) __s32 value; // 事件值 (e.g., 坐标值, 0/1表示按下/松开) };
- 写入设备节点:驱动将这个
input_event
写入到对应的/dev/input/eventX
(X通常是数字,如event0)字符设备节点中。这个节点是内核空间与用户空间进行交互的接口
第二阶段:用户空间 - EventHub
和 InputReader
这两个组件在 system_server
进程中,是 InputManagerService
的核心组成部分
1. EventHub
- 职责:监控和读取所有
/dev/input/
目录下的输入设备节点。它是硬件事件的直接来源 - 工作机制:
○ 使用inotify
监控/dev/input/
目录,以便在设备热插拔(如USB键盘插入拔出)时动态添加或移除设备
○ 使用epoll
机制轮询所有已打开的输入设备节点。当某个设备有数据可读时(即用户有输入动作),epoll
会通知EventHub
○EventHub
从对应的设备节点中读出原始的input_event
数据,并将其简单封装为一个RawEvent
对象
○InputReader
线程会不断地从EventHub
中getEvents()
,获取这些RawEvent
队列
2. InputReader
- 职责:解析和加工
RawEvent
。它是理解输入事件含义的“翻译官” - 工作机制:
○ 它运行在一个独立的线程(InputReader Thread)中,不断循环调用EventHub->getEvents()
,拿到一揽子RawEvent
○ 它持有所有输入设备的配置信息(InputDevice
),如键盘布局、触摸屏校准参数、按键映射等
○ 根据RawEvent
的type
和code
,将多个连续的原始事件组合成一个有逻辑意义的输入事件
◎ 例如:一个触摸操作通常会产生多个RawEvent
:一个EV_ABS
(ABS_X) 事件报告X坐标,一个EV_ABS
(ABS_Y) 事件报告Y坐标,一个EV_KEY
(BTN_TOUCH) 事件报告按下状态,最后是一个EV_SYN
(SYN_REPORT) 事件表示报告结束。InputReader
会等待SYN_REPORT
到来后,才认为一个完整的触摸点数据准备好了
○ 加工完成后,InputReader
会生成更高级的、Android框架定义的事件对象,如KeyEvent
(按键事件)、MotionEvent
(触摸/运动事件)
○ 策略处理:在这个过程中,它会应用一些输入策略,如:
◎ 设备映射:将游戏手柄的某个按键映射为“返回”键
◎ 输入校准:对触摸屏坐标进行校准
◎ 虚拟键处理:处理一些设备的电容导航键
○ 最后,InputReader
调用InputDispatcher->notifyXXX()
方法,将加工好的事件放入InputDispatcher
的队列中
第三阶段:用户空间 - InputDispatcher
- 职责:事件的派发和仲裁。它是输入事件的“交通警察”,决定事件最终发给哪个应用窗口
- 工作机制:
○ 它运行在另一个独立的线程(InputDispatcher Thread)中
○ 它维护了一个来自InputReader
的输入事件队列
○ 寻找目标窗口:当一个新事件到来时,InputDispatcher
会根据当前系统的状态(哪些窗口是活动的、它们的布局位置、焦点状态等)来计算事件应该派发给哪个窗口(WindowState
)
◎ 焦点窗口:按键事件通常派发给当前获得焦点的窗口(如输入框)
◎ 触摸命中测试:触摸事件会根据窗体的位置和触摸点坐标进行命中测试(Hit Test)。InputDispatcher
会向WindowManagerService
(WMS) 查询,找到包含该坐标点的、最顶层的、可见的窗口
○ 应用策略:在此阶段,会应用一些系统策略,如:
◎ 输入超时:防止应用ANR(Application Not Responding)。如果窗口在5秒内没有处理完一个输入事件,系统会弹出ANR对话框
◎ 事件过滤:过滤掉一些系统级快捷键(如Power键、Volume键),这些事件可能会被系统优先消费
◎ 虚拟键处理:Home、Back、Recents键的处理逻辑
○ 派发事件:
◎ 目标窗口确定后,InputDispatcher
会通过 SocketPair(一个双向的IPC通道)将事件发送到目标窗口所在的应用进程
◎ 派发是异步的。InputDispatcher
会等待应用处理完毕的“反馈”(finishInputEvent
)
◎ 如果事件是按键事件(如Back键),InputDispatcher
会先执行一个 “预派发” (Pre-dispatching)流程,将事件先发给当前焦点View,如果它处理了,流程结束;如果没处理,再继续正常的派发流程,可能会最终交给Activity处理
第四阶段:应用进程 - 从 ViewRootImpl
到 View
事件现在离开了 system_server
,进入了目标应用进程
- Socket监听与接收:
○ 每个应用的UI主线程都通过Looper
监听一个特殊的InputChannel
(它封装了SocketPair)
○ 当InputDispatcher
将事件写入Socket后,应用端的InputChannel
就会收到通知 ViewRootImpl$WindowInputEventReceiver
:
○ 在应用端,ViewRootImpl
内部的WindowInputEventReceiver
的onInputEvent()
方法被调用,它接收到了原始的输入事件InputEventReceiver
和InputStage
:
○ViewRootImpl
内部有一个InputStage
责任链模式的处理管道。事件会依次经过不同的InputStage
,每个阶段都有机会处理或消费事件
○ 阶段示例:
◎NativePreImeStage
:处理本地预输入法事件(如手柄按键)
◎ViewPreImeStage
:View树的预输入法阶段
◎ImeStage
:将事件派发给输入法(IME)。如果当前有输入法窗口,按键事件会先到这里。输入法可以消费掉事件(如输入文字),也可以选择不消费并让事件继续传递(如按下的方向键)
◎EarlyPostImeStage
:早期后输入法阶段,处理一些原始事件
◎ViewPostImeStage
:这是最关键的一步。在这里,事件被封装成Java层的KeyEvent
或MotionEvent
,并开始进入View树的分发流程- View树分发:
○ 起点:分发从DecorView
(根View)的dispatchPointerEvent()
开始
○ 传递路径:DecorView
->PhoneWindow
->ContentView
(通常是FrameLayout
) -> ... -> 最终的目标View
○ 分发规则:
◎ 触摸事件 (MotionEvent
):遵循onInterceptTouchEvent
和onTouchEvent
的机制。从父View到子View传递,父View可以拦截(onInterceptTouchEvent
返回true),子View可以消费(onTouchEvent
返回true)
◎ 按键事件 (KeyEvent
):从子View到父View传递。首先会尝试分发给当前获得焦点的View,如果它没有消费,则会依次向上传递给它的父View - 事件消费与反馈:
○ 当事件被View树中的某个View的onTouchEvent
或onKeyEvent
方法处理(返回true
)后,处理流程结束
○ 反馈:处理完成后,会沿着原路返回一个“完成”的信号(finishInputEvent
),通过InputChannel
和SocketPair最终回传给InputDispatcher
○InputDispatcher
收到反馈后,才会继续派发下一个输入事件。如果超时未收到反馈,就会触发ANR
总结与特点
- 生产者-消费者模型:
InputReader
是生产者,不断生产事件;InputDispatcher
是消费者和二次生产者;应用是最终的消费者 - 多线程设计:
InputReader
和InputDispatcher
是两个独立的线程,避免了事件读取阻塞事件派发 - 异步非阻塞:派发过程是异步的,通过SocketPair进行IPC,效率高
- 中心化仲裁:
InputDispatcher
和WindowManagerService
协同工作,是输入事件的唯一仲裁中心,确保了系统级策略的统一应用 - ANR机制:超时机制保证了系统的响应性