18、输入系统:EventHub、InputReader、InputDispatcher
说到Android的输入系统,很多人第一反应就是「点一下屏幕,应用响应了」。但中间发生了什么?我当年刚接触这部分源码时,也是一头雾水。说白了,从你手指触摸屏幕到应用层的onTouchEvent被调用,中间经过了三个核心角色:EventHub、InputReader、InputDispatcher。
这三个家伙分工明确,各司其职。我习惯把它们比作一个快递系统:EventHub是收件员,InputReader是分拣员,InputDispatcher是派送员。今天我们就来拆解一下这个流程。
18.1 整体架构概览
先看一张图,帮你建立整体认知。这是Android输入系统的核心流程:
嗯,这张图把三个核心模块的上下游关系画清楚了。接下来我们逐个深入。
18.2 EventHub — 硬件事件的「收件员」
EventHub 是输入系统的最底层。它直接跟 Linux 内核的输入子系统打交道,从 /dev/input/ 目录下读取原始事件。
我个人习惯把 EventHub 看作一个「事件采集器」。它做的事情其实很简单:
- 打开所有输入设备节点
- 监听设备的热插拔(比如你插了个USB键盘)
- 通过 epoll 机制等待事件到来
- 读取原始
input_event结构体
来看一段核心代码,这是 EventHub 中读取事件的逻辑:
// frameworks/native/services/inputflinger/EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
// 使用 epoll 等待事件
int eventCount = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
for (int i = 0; i < eventCount; i++) {
// 读取原始 input_event
struct input_event iev;
ssize_t n = read(deviceFd, &iev, sizeof(iev));
// 封装成 RawEvent
buffer[bufferCount].deviceId = deviceId;
buffer[bufferCount].type = iev.type;
buffer[bufferCount].code = iev.code;
buffer[bufferCount].value = iev.value;
bufferCount++;
}
return bufferCount;
}
getevent 命令直接查看 EventHub 输出的原始事件。我在项目中排查触摸屏不灵敏问题时,就是靠这个命令定位到是硬件上报频率太低。
18.3 InputReader — 事件的「翻译官」
EventHub 读出来的原始事件,说白了就是一堆数字:type=3, code=53, value=720。这谁能看懂?InputReader 就是干这个翻译活的。
InputReader 运行在独立的 InputReader 线程中。它会:
- 根据设备类型创建对应的 InputMapper(比如触摸屏用 TouchInputMapper,键盘用 KeyboardInputMapper)
- 将原始事件映射成 Android 的 MotionEvent 或 KeyEvent
- 进行坐标转换、多点触控协议解析等
举个例子,触摸屏上报的原始坐标可能是 0-4095 的 ADC 值,InputReader 需要把它转换成屏幕分辨率下的像素坐标。我曾经遇到过一个坑:某款平板触摸屏的坐标映射反了,点左上角却响应右下角。查了半天,发现是 InputReader 中的坐标转换矩阵配置错了。
// 伪代码:InputReader 处理流程
void InputReader::loopOnce() {
// 1. 从 EventHub 获取原始事件
RawEvent rawEvents[MAX_RAW_EVENTS];
size_t count = mEventHub->getEvents(timeout, rawEvents, MAX_RAW_EVENTS);
// 2. 交给对应的 Mapper 处理
for (size_t i = 0; i < count; i++) {
InputMapper* mapper = getMapperForDevice(rawEvents[i].deviceId);
mapper->process(&rawEvents[i]);
}
// 3. 将处理好的事件放入队列
mQueuedListener->flush();
}
18.4 InputDispatcher — 事件的「派送员」
InputDispatcher 是输入系统的最后一环,也是最复杂的一环。它运行在 InputDispatcher 线程中,负责把事件分发给目标窗口。
你想想看,屏幕上可能同时有多个窗口:状态栏、桌面、悬浮窗、弹窗……手指点下去,事件该给谁?
InputDispatcher 通过 焦点窗口(Focused Window) 机制来决定。它会维护一个窗口列表,并根据窗口的 Z-order 和焦点状态进行分发。
分发流程大致如下:
- 从队列中取出事件
- 查找当前焦点窗口
- 检查窗口是否有 ANR 风险(如果窗口5秒内没有处理上一个事件,就会触发 ANR)
- 通过 InputChannel 将事件发送到目标窗口
- 等待窗口处理完成(同步事件需要等待)
// 伪代码:InputDispatcher 分发流程
void InputDispatcher::dispatchOnce() {
// 取出待分发的事件
EventEntry* entry = mInboundQueue.dequeue();
// 找到目标窗口
Vector<InputTarget> targets;
findTargets(entry, &targets);
// 分发到每个目标
for (size_t i = 0; i < targets.size(); i++) {
InputChannel* channel = targets[i].inputChannel;
// 通过 socket 发送事件
channel->sendMessage(entry);
}
}
18.5 三个模块的协作关系
这三个模块虽然分工不同,但协作非常紧密。我画了一张时序图,帮你理解它们之间的调用关系:
18.6 实战中的常见问题
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| 触摸无响应 | EventHub 未读到事件 | 用 getevent 检查硬件是否上报 |
| 触摸位置偏移 | InputReader 坐标转换错误 | 检查 idc 配置文件中的坐标映射 |
| 点击后 ANR | InputDispatcher 超时 | 检查应用层 onTouchEvent 是否有耗时操作 |
| 多点触控异常 | 协议解析错误 | 确认内核上报的是 Type A 还是 Type B 协议 |
dumpsys input 查看当前输入系统的状态,包括所有输入设备、焦点窗口、待分发事件队列等。我每次排查输入问题,第一件事就是跑这个命令。
好了,关于输入系统的三个核心组件就讲到这里。EventHub 负责收件,InputReader 负责翻译,InputDispatcher 负责派送。理解了这个链条,你就能在遇到输入问题时,快速定位是硬件层、系统层还是应用层的问题。