InputReader:原始事件如何变成 MotionEvent
InputReader:原始事件如何变成 MotionEvent
上一章我们拿到了内核原始事件。这一章回答:这些 type/code/value 是怎么被加工成 MotionEvent、KeyEvent,并进入分发队列的?
InputReader 消费的是 EventHub 交出的原始记录。它识别设备类型和事件协议,维护按键或触点 状态,执行旋转、缩放等坐标变换,最终通过监听接口把规范化事件交给 InputDispatcher。
InputReader 加工事件,不选择应用窗口
| 常见误解 | 正确理解 |
|---|---|
| MotionEvent 是内核直接产生的 | 内核只给原始事件,MotionEvent 由 InputReader 加工而来 |
| InputReader 把事件直接发给应用 | 它只把统一事件放进 InputDispatcher 的队列,分发在下一环 |
| 触摸坐标拿来就能用 | 设备坐标要经过屏幕旋转、缩放等变换才能对应屏幕坐标 |
应用拿到的坐标已经经过 reader 阶段的设备映射与显示变换。此时仍未确定目标窗口:触摸 命中、按键焦点和通道投递属于 InputDispatcher 的职责。
核心结论
- InputReader 线程循环调用
EventHub.getEvents(),拿到原始事件后按设备分类处理。 - 触摸屏原始事件被组装成
MotionEvent:按下、移动、抬起对应 ACTION_DOWN/MOVE/UP。 - 坐标要经过变换:设备坐标 → 屏幕方向/缩放校正后的坐标。
- 键盘与按键原始事件被组装成
KeyEvent,并执行按键策略。 - 加工完的事件进入 InputDispatcher 的入站队列,reader 不负责找窗口。
加工主链
逐步解释:
- 循环取件:InputReader 线程每轮调用
getEvents(),拿到一批原始事件。 - 分类:按设备的类型(触摸屏、触摸板、键盘、鼠标等)走不同处理分支。
- 组装:把
SYN_REPORT之间的一组原始事件合成一次MotionEvent或KeyEvent。 - 策略:处理屏幕旋转、按键映射、防误触等系统级规则。
- 交接:统一事件进入 InputDispatcher 的入站队列,等待分发。
坐标变换为什么在这里
设备上报的坐标通常是物理轴上的原始值,与屏幕方向、分辨率并不直接一致。InputReader 阶段会结合设备参数与显示方向做变换,例如竖屏/横屏切换时坐标轴交换。因此应用拿到的 坐标已经是“当前屏幕坐标语义”下的值;调试时如果发现坐标方向不对,优先怀疑这一环的 变换配置。
源码证据
阅读目标:确认 reader 的循环入口与事件组装位置。
正文指针:InputReader.cpp loopOnce()/processEventsLocked(); InputDevice.cpp 中按设备类别的加工入口。
// InputReader.cpp(伪代码,行号以 r75 为准)
void InputReader::loopOnce() {
size_t count = mEventHub->getEvents(timeout, mEventBuffer, ...);
processEventsLocked(mEventBuffer, count); // 分类与组装
// 把事件交给 InputDispatcher
}这段代码证明: reader 的每一轮都从 EventHub 拿原始事件并立即加工;事件加工完成后 通过队列交给 dispatcher,两者是“生产-消费”关系,各自拥有独立线程。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell dumpsys input | sed -n '/Input Reader State/,/Input Dispatcher State/p'
adb logcat -v threadtime | grep -E "InputReader|MotionEvent|InputDispatcher"dumpsys input 的 “Input Reader State” 段落会列出设备、配置与部分状态;logcat 可以 观察事件日志。触摸失效时,如果 getevent 有输出但这里看不到有效事件,问题就在加工阶段 (配置、策略或设备被禁用)。
常见误区
- “MotionEvent 来自内核”:来自 InputReader 的加工。
- “reader 直接找窗口”:找窗口是 dispatcher 的职责。
- “原始坐标等于屏幕坐标”:需要经过变换。
- “每个原始事件都是一个 MotionEvent”:一组原始事件才合成一次 MotionEvent。
延伸问题
InputReader如何区分触摸屏与触摸板(PointerController 差异)?- 屏幕旋转时坐标变换在哪一步发生,横竖屏切换如何验证?
- 设备被禁用(disableInputDevice)后,reader 如何处理?
MotionEvent的 downTime/eventTime 来自哪组原始事件?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
InputReader.cpp | loopOnce() | 每轮读取与加工 |
InputReader.cpp | processEventsLocked() | 事件分类与组装 |
InputDevice.cpp | 各类设备加工入口 | 触摸/按键/鼠标分支 |
InputDispatcher.cpp | enqueueInboundEventLocked() | 入站队列交接点 |
公共路径:frameworks/base/services/inputflinger/reader/ 与 frameworks/base/services/inputflinger/dispatcher/。行号以 r75 检索为准。
事件如何选择目标窗口,见 InputDispatcher。