4. Android 输入系统架构:EventHub、InputReader、InputDispatcher 与触摸事件分发流程
这一章,我们来聊聊 Android 输入系统的骨架。说白了,就是一根手指从触摸屏点下去,到上层应用响应,中间到底经历了什么。
我刚开始接触 Android 驱动时,总觉得上层框架太抽象,不就是个中断上报吗?后来踩了坑才明白——驱动只是起点,真正的战场在框架层。触摸偏移的补偿,很多时候不是在驱动里改个坐标就完事的,你得理解 EventHub 怎么读数据、InputReader 怎么解析、InputDispatcher 怎么派发,才能精准定位问题。
4.1 整体架构:一张图看懂
先看整体流程。我习惯把 Android 输入系统画成下面这张图,每次排查触摸偏移问题时,我都会对着它捋一遍数据流。
嗯,这张图我每次培训都会用。你看,从下往上四层,每一层都有它的职责。我们逐层拆解。
4.2 EventHub:原始事件的守门人
EventHub 是 Android 输入系统的入口。它做的事情很纯粹——打开 /dev/input/eventX 设备节点,用 epoll 监听,一旦有数据就 read 出来。
我记得有一次,客户反馈触摸偶尔断触。我第一反应是驱动有问题,结果抓了 EventHub 的日志,发现 read 回来的数据包长度不对。原来是某个第三方触控 IC 在睡眠唤醒后,上报的事件格式变了,EventHub 解析时直接丢弃了异常包。你看,问题不在驱动,在 EventHub 的容错逻辑。
getevent -lt 可以实时查看 EventHub 读到的原始事件。如果这里的数据就不对,那上层再怎么校准都是白搭。
EventHub 的核心数据结构是 RawEvent,它长这样:
struct RawEvent {
nsecs_t when; // 时间戳
int32_t deviceId; // 设备 ID
int32_t type; // 事件类型 (EV_ABS, EV_KEY...)
int32_t code; // 事件码 (ABS_MT_POSITION_X, BTN_TOUCH...)
int32_t value; // 事件值 (坐标值、按键状态)
};
说白了,EventHub 就是个搬运工。它不关心坐标对不对,也不管多点触控的协议是 A 还是 B,它只负责把内核上报的 input_event 结构体原封不动地搬到上层。
4.3 InputReader:坐标校准的第一道防线
InputReader 才是真正干活的地方。它从 EventHub 拿到 RawEvent,然后做三件事:
- 协议解析:区分单点、多点(Protocol A/B),把原始数据组装成完整的触摸点信息。
- 坐标变换:根据
InputDevice的配置,对原始坐标做缩放、旋转、偏移补偿。 - 事件合成:生成
NotifyArgs结构体,交给 InputDispatcher。
这里有个关键点——坐标变换矩阵。Android 允许为每个输入设备配置一个 3x3 的仿射变换矩阵,定义在 /system/usr/idc/ 下的 .idc 文件中。我处理过一个项目,触摸屏贴了偏光片后,Y 轴整体偏移了 15 个像素。驱动改起来周期长,我直接在 .idc 里加了一行:
touch.deviceType = touchScreen
touch.orientationAware = 1
touch.size.calibration = diameter
# 坐标偏移补偿
touch.scaleX = 1.0
touch.scaleY = 1.0
touch.offsetX = 0
touch.offsetY = -15
重启后立竿见影。这就是 InputReader 层的威力——不改驱动,不改应用,只改配置文件就能解决偏移。
4.4 InputDispatcher:事件派发的交通警察
InputDispatcher 拿到 InputReader 送来的事件后,开始做派发决策。它的核心逻辑是:
- 查找目标窗口:根据触摸点的坐标,找到当前焦点窗口或触摸区域对应的窗口。
- 事件排队:把事件放入目标窗口的输入队列。
- 唤醒应用线程:通过 Looper 机制通知应用处理事件。
这里有个容易踩坑的地方——窗口坐标变换。应用窗口可能做了缩放、旋转或者有状态栏偏移。InputDispatcher 在派发时,会把屏幕坐标转换成窗口坐标。如果这个转换逻辑有 bug,触摸点就会在应用里偏掉。
我曾经遇到一个 case:某款平板在横竖屏切换后,触摸点总是偏左上角。查了两天,最后发现是 InputDispatcher 在计算窗口变换矩阵时,没有考虑屏幕旋转后的宽高交换。说白了,就是 displayInfo.orientation 变了,但 inputWindowHandle 里的变换矩阵没更新。
| 层级 | 职责 | 偏移补偿方式 | 调试手段 |
|---|---|---|---|
| EventHub | 读取原始事件 | 无(仅透传) | getevent -lt |
| InputReader | 解析 + 校准 | .idc 配置、坐标变换矩阵 | dumpsys input |
| InputDispatcher | 派发 + 窗口变换 | 窗口坐标转换 | dumpsys window |
| 应用层 | 接收并处理 | 软件补偿(onTouchEvent) | MotionEvent 日志 |
4.5 触摸事件分发流程:从手指到应用
把上面三层串起来,就是完整的分发流程。我习惯用下面这个步骤来记忆:
- 硬件中断:触摸屏 IC 检测到触摸,通过 I2C/SPI 上报坐标数据。
- 内核驱动:生成
input_event,写入/dev/input/eventX。 - EventHub:epoll 唤醒,read 出
RawEvent。 - InputReader:解析协议,应用校准,生成
NotifyArgs。 - InputDispatcher:查找目标窗口,转换坐标,派发事件。
- ViewRootImpl:应用主线程收到事件,分发给
View.dispatchTouchEvent()。 - 应用处理:
onTouchEvent()或onInterceptTouchEvent()响应。
你看,从第 1 步到第 7 步,每一步都可能引入偏移。但反过来,每一步也都可以做补偿。我个人建议的排查顺序是:先看 EventHub 的原始数据对不对 → 再看 InputReader 的校准参数是否生效 → 最后查 InputDispatcher 的窗口变换。按这个顺序走,90% 的触摸偏移问题都能定位。
adb shell dumpsys input 查看 InputReader 的校准参数和 InputDispatcher 的窗口信息。重点关注 TouchCalibration 和 WindowTransform 两个字段。如果它们和预期一致,那问题大概率在应用层。