本文聚焦于 Android 输入系统中InputReader 线程的工作原理,详细解析其如何从硬件设备读取原始事件、加工处理并传递给后续模块。以下以通俗语言和生动类比,带你理解 InputReader 的核心流程。
一、InputReader 线程的起点:循环处理事件
InputReader 线程是 Input 系统的 “事件快递员”,它的工作从threadLoop
方法开始,通过不断循环调用loopOnce
来处理事件,就像快递员反复从传送带取件分拣一样。
1.1 核心循环:loopOnce 的使命
cpp
bool InputReaderThread::threadLoop() {
mReader->loopOnce(); // 循环处理事件
return true; // 持续循环
}
-
目标:每次循环完成三个核心任务:
- 从 EventHub 获取原始事件(类似从快递站取包裹)。
- 处理事件(解析包裹信息,分类打包)。
- 传递事件给 InputDispatcher(交给下一个分拣环节)。
二、EventHub:硬件事件的 “翻译官”
EventHub 是 InputReader 的 “事件接收站”,负责监听硬件设备(如触摸屏、键盘)的事件,并将原始内核事件转换为系统可识别的格式。
2.1 事件获取:从设备到 RawEvent
cpp
size_t EventHub::getEvents(...) {
// 通过epoll监听/dev/input设备节点
int pollResult = epoll_wait(mEpollFd, ...);
// 读取原始事件(如触摸坐标、按键扫描码)
read(device->fd, readBuffer, ...);
// 将内核事件(input_event)转换为RawEvent(带设备ID的标准化事件)
event->when = ...;
event->deviceId = ...;
}
-
关键技术:
- epoll + inotify:高效监控设备文件变化,类似快递站的监控系统,实时感知新包裹到达或旧包裹移除。
- RawEvent:将内核事件包装为包含时间、设备 ID、类型等信息的结构体,如同给包裹贴上 “分拣标签”。
2.2 设备扫描:发现新硬件
当插入新设备(如外接键盘)时,EventHub 会扫描/dev/input
目录,创建设备对象并注册到 epoll 监听:
cpp
void EventHub::scanDevicesLocked() {
// 遍历/dev/input下的所有文件
while((de = readdir(dir))) {
// 打开设备文件,获取设备信息(名称、类型等)
openDeviceLocked(devname);
// 将设备添加到epoll监听列表
epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, ...);
}
}
- 类比:快递站收到新包裹时,先登记包裹信息(设备 ID、类型),再放入传送带(epoll 队列)等待分拣。
三、InputReader 处理事件:从原始数据到标准事件
InputReader 拿到 RawEvent 后,开始 “加工处理”:根据设备类型(键盘、触摸屏等)使用不同的 “映射器”(InputMapper)解析事件,并生成系统级事件(如 KeyEvent)。
3.1 事件分类处理
cpp
void InputReader::processEventsLocked(...) {
for (const RawEvent* rawEvent : rawEvents) {
if (rawEvent->type == DEVICE_ADDED) {
addDeviceLocked(...); // 处理设备添加
} else if (rawEvent->type == EV_KEY) {
processEventsForDeviceLocked(...); // 处理按键事件
}
}
}
-
事件类型:
- 设备事件:添加 / 移除设备(如插入鼠标)。
- 数据事件:具体操作事件(如按下按键、滑动屏幕)。
3.2 设备添加:创建 InputDevice 和映射器
当新设备(如键盘)接入时,InputReader 会为其创建InputDevice
对象,并根据设备类型添加对应的 “映射器”(InputMapper):
cpp
InputDevice* InputReader::createDeviceLocked(...) {
// 创建InputDevice对象
InputDevice* device = new InputDevice(...);
// 根据设备类型添加映射器
if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
device->addMapper(new KeyboardInputMapper(...)); // 键盘映射器
} else if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
device->addMapper(new MultiTouchInputMapper(...)); // 多点触摸映射器
}
}
-
映射器的作用:
- KeyboardInputMapper:将按键扫描码(如硬件特有的 0x1e)转换为 Android 标准键码(如 AKEYCODE_A)。
- MultiTouchInputMapper:解析多点触摸坐标,生成 MotionEvent。
3.3 按键事件处理示例
以键盘按下为例,InputReader 的处理流程如下:
-
原始事件读取:从 EventHub 获取 RawEvent(type=EV_KEY,code = 扫描码)。
-
扫描码转键码:通过 KeyCharacterMap 将扫描码(如 0x1e)转换为 Android 键码(如 AKEYCODE_A)。
-
生成通知参数:创建
NotifyKeyArgs
对象,包含事件时间、键码、按下状态等信息。
cpp
void KeyboardInputMapper::processKey(...) {
// 扫描码转键码
getEventHub()->mapKey(...);
// 生成通知参数
NotifyKeyArgs args(...);
// 通知队列接收事件
getListener()->notifyKey(&args);
}
四、事件传递:从 InputReader 到 InputDispatcher
处理后的事件需要传递给 InputDispatcher 进行分发,这一过程通过QueuedInputListener
实现,它是两者之间的 “传送带”。
4.1 事件入队:暂存待发送事件
cpp
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
mArgsQueue.push(new NotifyKeyArgs(*args)); // 事件入栈
}
- 类比:快递员将处理好的包裹放入暂存区(队列),等待运输车(InputDispatcher)来取。
4.2 事件发送:唤醒 InputDispatcher
cpp
void QueuedInputListener::flush() {
for (NotifyArgs* args : mArgsQueue) {
args->notify(mInnerListener); // mInnerListener是InputDispatcher
delete args;
}
mArgsQueue.clear();
}
-
关键步骤:
- 遍历队列:将
NotifyKeyArgs
转换为KeyEntry
(EventEntry 的子类)。 - 加入 InputDispatcher 队列:将事件放入 InputDispatcher 的
mInboundQueue
。 - 唤醒线程:通过 Looper 的
wake
方法通知 InputDispatcher 有新事件需要处理,类似给运输车司机发信号 “有新包裹,快来取”。
- 遍历队列:将
五、核心机制总结
1. 事件处理流水线
plaintext
硬件设备(如键盘) → EventHub(读取原始事件) → InputReader(解析、映射) →
QueuedInputListener(暂存) → InputDispatcher(分发到应用)
2. 关键组件作用
- EventHub:硬件事件的 “翻译官”,负责监听设备并转换原始事件。
- InputReader:事件的 “加工工厂”,通过映射器将原始事件转换为系统标准事件。
- InputMapper:针对不同设备的 “翻译员”(如键盘翻译员、触摸翻译员)。
- QueuedInputListener:事件传递的 “传送带”,连接 InputReader 和 InputDispatcher。
3. 线程协作
- InputReaderThread:专注读取和处理事件,不阻塞其他操作。
- InputDispatcherThread:专注事件分发,通过 Looper 机制实现高效唤醒。
六、常见场景与延伸思考
Q:如何处理多点触摸事件?
- A:通过
MultiTouchInputMapper
解析原始触摸坐标,生成包含多个指针信息的 MotionEvent,确保每个触点都能被正确识别。
Q:为什么需要扫描设备?
- A:当设备插入或移除时,系统需要动态更新可用输入源(如外接键盘插入后,InputReader 需识别并处理其事件)。
Q:如何调试输入事件?
-
A:
- 使用
adb shell getevent
命令查看原始设备事件。 - 跟踪日志关键词(如
InputReader
、EventHub
),定位事件处理异常点。
- 使用
总结
InputReader 线程是 Android 输入系统的 “前哨站”,它通过高效的事件获取、解析和转换机制,将硬件产生的原始信号转化为系统可理解的标准事件,并传递给后续模块。理解这一过程有助于深入掌握输入系统的工作原理,对优化输入响应速度、处理兼容性问题等场景具有重要意义