1. Input系统整体架构:从触摸屏到应用层的完整链路
大家好,欢迎来到《Input系统事件分发源码深度剖析》的第一章。
今天我们来聊聊Android Input系统的整体架构。说白了,就是你的手指在屏幕上点了一下,这个动作是怎么一步步传到App里的某个按钮上的。这条链路很长,但我会尽量讲得通俗些。
我个人习惯把Input系统想象成一条流水线。触摸屏是原材料入口,应用层是成品出口。中间有几个关键的加工车间。嗯,咱们今天就先看看这条流水线长什么样。
1.1 整体链路概览
先画个简单的图在脑子里:
触摸屏硬件 → 内核驱动 → EventHub → InputReader → InputDispatcher → 窗口 → 应用层
这条链路上,最核心的两个角色就是 InputReader 和 InputDispatcher。它们都运行在系统进程 system_server 中,属于同一个线程——InputReader线程和InputDispatcher线程。
你可能会问:为什么要把它们分开?
我刚开始研究这部分源码时也有这个疑问。后来在项目中遇到过一次触摸卡顿的问题,排查下来才发现,如果让Reader和Dispatcher合在一起,读取事件时如果分发阻塞,整个系统都会卡住。所以Google把它们拆成了两个独立线程,各司其职。
1.2 InputReader:从硬件到事件的翻译官
InputReader的职责很明确:从内核读取原始事件,解析成Android能理解的事件结构。
它通过EventHub与内核驱动交互。EventHub会监听 /dev/input/ 目录下的设备节点。每当有触摸事件产生,内核通过 read() 系统调用把原始数据传上来。
核心流程:
- EventHub 通过 epoll 监听输入设备
- InputReader 循环调用 EventHub::getEvents() 获取原始事件
- 将原始事件解析为 RawEvent 结构
- 通过 InputMapper 转换成 NotifyArgs
- 放入 InputDispatcher 的输入队列
这里有个关键点:InputMapper。不同的输入设备有不同的Mapper。触摸屏用 TouchInputMapper,键盘用 KeyboardInputMapper。我曾经在适配一款国产触摸屏时,发现上报的坐标总是偏移。排查到最后,是Mapper里的参数配置不对。嗯,这种坑踩过一次就记住了。
1.3 InputDispatcher:事件的路由与分发
InputDispatcher 是真正的"调度员"。它从InputReader那里拿到事件后,要做几件事:
- 策略查询:询问WindowManagerService,当前焦点窗口是谁
- 事件拦截:判断是否需要拦截(比如正在动画中)
- 超时监控:如果App处理事件超时,会触发ANR
- 有序分发:保证事件按顺序到达目标窗口
我建议你记住这个数据结构:QueuedInputEntry。Dispatcher内部维护了一个队列,所有待分发的事件都在里面排队。为什么要有队列?因为App处理事件是异步的,如果App还没处理完上一个事件,新事件就得等着。
避坑指南:
我曾经遇到过一个线上问题:某个页面滑动时偶尔会"跳帧"。查了半天,发现是InputDispatcher的队列里积压了太多事件。原因是App主线程在做耗时操作,导致事件处理不过来。解决方案很简单——把耗时操作移到子线程。但排查过程很痛苦,因为你要从Input系统一路追到App层。
1.4 InputReader与InputDispatcher的分工对比
| 维度 | InputReader | InputDispatcher |
|---|---|---|
| 职责 | 读取原始事件,解析成标准格式 | 将事件分发给目标窗口 |
| 输入 | 内核驱动的原始数据 | InputReader处理后的NotifyArgs |
| 输出 | 放入Dispatcher的输入队列 | 发送到App的ViewRootImpl |
| 线程 | InputReader线程 | InputDispatcher线程 |
| 关键类 | EventHub, InputMapper | InputChannel, Connection |
| 常见问题 | 设备节点读取失败、Mapper配置错误 | ANR、事件丢失、焦点错乱 |
你看这个表格就清楚了。Reader负责"翻译",Dispatcher负责"送信"。两者通过一个共享的 Queue 进行通信。这个队列是线程安全的,内部用了锁机制。
1.5 从触摸屏到应用层的完整流程
咱们走一遍完整的流程,你感受一下:
- 硬件层:手指触摸屏幕,电容变化产生电信号
- 内核驱动:驱动解析电信号,生成
input_event结构,写入/dev/input/eventX - EventHub:通过epoll监听到fd可读,调用
read()读取原始数据 - InputReader:将原始数据封装成
RawEvent,通过TouchInputMapper处理坐标、压力等信息 - InputDispatcher:从队列取出事件,查询焦点窗口,通过
InputChannel发送 - ViewRootImpl:接收事件,通过
View.dispatchTouchEvent()开始分发 - 应用层:事件到达具体的View,触发
onTouchEvent()
注意:
整个流程中,从第1步到第5步都是在系统进程完成的。只有第6步才进入App进程。这意味着,如果系统进程卡顿,所有App的触摸事件都会受影响。我记得有一次系统升级后,所有App都出现触摸延迟,最后定位到是InputReader线程里加了一个不必要的日志打印。
1.6 小结
这一章我们梳理了Input系统的整体架构。核心就两个角色:
- InputReader:负责从硬件读取并解析事件
- InputDispatcher:负责将事件分发给目标窗口
下一章我们会深入InputReader的源码,看看它到底是怎么从内核读取事件的。到时候会涉及到epoll、EventHub的细节,还有那个让我踩过坑的InputMapper配置。
嗯,今天就到这里。记住这条链路,后面的章节都会围绕它展开。