一、触摸系统概述:AOSP输入系统架构全景
各位同学,欢迎来到《AOSP触摸与输入系统移植实战课程》。我是你们的老朋友,一个在Android底层摸爬滚打了多年的工程师。
今天咱们聊点硬核的——触摸系统。说实话,我刚接触AOSP输入系统那会儿,也被它绕得头晕。EventHub、InputReader、InputDispatcher……这些名字听起来就像三个互不相干的黑盒子。但等你真正搞懂了它们之间的协作关系,你会发现,嗯,其实没那么复杂。
先看一张全景图。整个输入系统,说白了就是一条流水线:
- 硬件层:触摸屏、按键、鼠标等物理设备产生原始信号
- 内核层:Linux Input子系统将信号封装成标准事件(struct input_event)
- EventHub:从设备节点读取原始事件
- InputReader:解析原始事件,转换成Android内部事件
- InputDispatcher:把事件分发给目标窗口
- 应用层:View.onTouchEvent() 等回调处理
你想想看,从你手指碰到屏幕,到App收到MotionEvent,中间经历了这么多环节。任何一个环节出问题,触摸就卡顿、断触或者干脆没反应。我在项目中遇到过好几次,明明硬件没问题,就是EventHub读不到事件,最后发现是selinux权限没配好——这种坑,踩一次就记住了。
核心要点:AOSP输入系统是一个典型的生产者-消费者模型。EventHub是生产者,InputReader是加工者,InputDispatcher是分发者。三者通过管道和共享内存通信。
二、EventHub与InputReader工作原理
2.1 EventHub:最底层的“搬运工”
EventHub,名字起得很直白——事件集线器。它的工作就是盯着 /dev/input/ 目录下的所有设备节点。
我习惯把EventHub比作一个“守门人”。它用epoll机制同时监听多个设备文件。一旦有数据可读,立刻通知上层。
// EventHub的核心循环(简化版)
while (true) {
int ret = epoll_wait(mEpollFd, events, EPOLL_MAX_EVENTS, -1);
for (int i = 0; i < ret; i++) {
if (events[i].data.fd == mWakeReadPipeFd) {
// 唤醒事件,处理命令
} else {
// 读取设备节点数据
struct input_event buffer[INPUT_EVENT_BUFFER_SIZE];
int count = read(events[i].data.fd, buffer, sizeof(buffer));
// 将原始事件放入队列
mEventBuffer.push_back(buffer, count);
}
}
}
这里有个细节要注意:EventHub读取的是Linux内核的input_event结构体,包含type、code、value三个字段。比如触摸屏按下,type是EV_ABS,code是ABS_MT_POSITION_X,value是x坐标值。
避坑指南:我曾经在移植一款触控屏时,发现EventHub能读到事件,但坐标值始终不对。查了两天才发现,是设备树的input上报格式配置错了——ABS_MT_POSITION_X的最大值设成了4095,但屏幕分辨率是1920。EventHub读到的是原始值,不会帮你做归一化。这个坑,大家移植时一定要检查。
2.2 InputReader:从“原始数据”到“内部事件”
EventHub把原始事件丢给InputReader,InputReader就开始干活了。它的核心任务就两个:
- 解析:把input_event转换成Android的RawEvent
- 合成:把多个原始事件合成一个完整的MotionEvent或KeyEvent
举个例子,触摸屏上报一个手指按下,内核会连续发送多个input_event:
// 内核上报的原始事件序列
EV_ABS / ABS_MT_SLOT / 0 // 当前操作的是第0个触点
EV_ABS / ABS_MT_TRACKING_ID / 123 // 触点的唯一ID
EV_ABS / ABS_MT_POSITION_X / 500 // X坐标
EV_ABS / ABS_MT_POSITION_Y / 300 // Y坐标
EV_SYN / SYN_REPORT / 0 // 同步信号,表示一次完整上报结束
InputReader收到这些事件后,会做两件事:
- 根据ABS_MT_SLOT和ABS_MT_TRACKING_ID判断是新增触点还是移动
- 等到SYN_REPORT信号,才把这一批事件打包成一个MotionEvent
嗯,这里要注意。如果触控屏驱动没有正确发送SYN_REPORT,InputReader就会一直等,导致触摸事件延迟甚至丢失。我在调试一款国产触控IC时就遇到过,驱动工程师忘了加SYN_REPORT,结果触摸响应慢了200ms——用户体验极差。
个人经验:调试触摸系统时,我建议先用getevent命令看原始事件流。如果getevent能正常显示坐标,但App收不到,问题大概率出在InputReader或InputDispatcher。如果getevent都看不到数据,那就要查EventHub和内核驱动了。
三、InputDispatcher事件分发流程
InputReader把事件准备好后,通过InputChannel发送给InputDispatcher。InputDispatcher是整个输入系统的“交通警察”,它决定事件该发给谁。
3.1 分发策略
InputDispatcher维护了一个窗口列表,每个窗口都有一个InputChannel。分发时,它根据以下规则选择目标:
- 触摸事件:发给当前触摸点下方的窗口(通过hit test计算)
- 按键事件:发给当前获得焦点的窗口
- 轨迹球事件:发给当前获得焦点的窗口
分发流程大致如下:
// InputDispatcher分发核心逻辑(伪代码)
void InputDispatcher::dispatchOnce() {
// 1. 从队列取出一个事件
EventEntry* entry = mInboundQueue.dequeue();
// 2. 找到目标窗口
Vector<InputTarget> targets;
findTargets(entry, &targets);
// 3. 遍历所有目标,发送事件
for (auto& target : targets) {
// 通过InputChannel发送
status_t status = target.inputChannel->sendMessage(entry);
if (status != OK) {
// 发送失败,可能是窗口已销毁
handleTargetDead(target);
}
}
// 4. 等待应用处理完成(同步事件需要)
if (entry->needWait) {
waitForAppProcess(entry);
}
}
3.2 同步与异步
你可能会问:为什么触摸事件有时候会丢?其实跟同步机制有关。
InputDispatcher支持两种模式:
| 模式 | 说明 | 典型场景 |
|---|---|---|
| 同步 | 等待应用处理完上一个事件,才发下一个 | 按键事件(防止乱序) |
| 异步 | 不等待,直接发送 | 触摸事件(追求低延迟) |
但异步模式有个隐患:如果应用处理速度跟不上,事件会堆积在InputChannel的缓冲区里。缓冲区满了,新事件就会被丢弃。这就是为什么有些手机在快速滑动时会断触——不是硬件问题,是软件处理不过来。
关键点:InputDispatcher的ANR机制也是基于这个同步等待。如果应用超过5秒没有处理完上一个事件,InputDispatcher就会认为应用ANR,弹出“应用无响应”对话框。我在项目中遇到过,某个第三方输入法在处理按键事件时卡住了,导致整个系统触摸都卡死——因为InputDispatcher被阻塞了。
3.3 事件注入与拦截
InputDispatcher还支持事件注入,比如通过adb shell input tap x y 模拟点击。这个功能在自动化测试中很常用。
注入事件的流程是:
- EventHub收到注入命令(通过mWakeReadPipeFd唤醒)
- InputReader解析注入事件
- InputDispatcher正常分发
但要注意,注入事件和真实事件在InputDispatcher内部是区分处理的。注入事件会带上一个INJECTION标志,系统可以根据这个标志做权限校验。
避坑指南:我曾经在移植一个车载系统时,需要支持方向盘按键控制中控屏。但方向盘按键是通过CAN总线转GPIO模拟的,上报的事件类型和标准按键不一样。我花了整整一周,才在InputReader里加了一个映射层,把非标事件转成标准KeyEvent。如果你也遇到类似需求,建议优先修改内核驱动,让上报的事件符合Linux Input协议规范——这样EventHub和InputReader就不用改了。
小结
好了,这一章的内容就到这里。我们梳理了AOSP输入系统的三大核心组件:
- EventHub:从内核读取原始事件,是系统的“耳朵”
- InputReader:解析原始事件,是系统的“大脑”
- InputDispatcher:分发事件给应用,是系统的“手”
下一章,我们会深入EventHub的源码,看看它到底是怎么管理设备节点的。到时候我会分享一些我在调试多点触控时的实战经验,保证干货满满。
记住一句话:搞懂输入系统,就是搞懂Android的“感知”能力。触摸不灵,别急着换屏,先查查EventHub和InputReader——很多时候,问题出在软件层。
我们下章见。