Input系统之InputReader线程

10 阅读5分钟

本文聚焦于 Android 输入系统中InputReader 线程的工作原理,详细解析其如何从硬件设备读取原始事件、加工处理并传递给后续模块。以下以通俗语言和生动类比,带你理解 InputReader 的核心流程。

一、InputReader 线程的起点:循环处理事件

InputReader 线程是 Input 系统的 “事件快递员”,它的工作从threadLoop方法开始,通过不断循环调用loopOnce来处理事件,就像快递员反复从传送带取件分拣一样。

1.1 核心循环:loopOnce 的使命

cpp

bool InputReaderThread::threadLoop() {
    mReader->loopOnce(); // 循环处理事件
    return true; // 持续循环
}
  • 目标:每次循环完成三个核心任务:

    1. 从 EventHub 获取原始事件(类似从快递站取包裹)。
    2. 处理事件(解析包裹信息,分类打包)。
    3. 传递事件给 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 的处理流程如下:

  1. 原始事件读取:从 EventHub 获取 RawEvent(type=EV_KEY,code = 扫描码)。

  2. 扫描码转键码:通过 KeyCharacterMap 将扫描码(如 0x1e)转换为 Android 键码(如 AKEYCODE_A)。

  3. 生成通知参数:创建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();
}
  • 关键步骤

    1. 遍历队列:将NotifyKeyArgs转换为KeyEntry(EventEntry 的子类)。
    2. 加入 InputDispatcher 队列:将事件放入 InputDispatcher 的mInboundQueue
    3. 唤醒线程:通过 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命令查看原始设备事件。
    • 跟踪日志关键词(如InputReaderEventHub),定位事件处理异常点。

总结

InputReader 线程是 Android 输入系统的 “前哨站”,它通过高效的事件获取、解析和转换机制,将硬件产生的原始信号转化为系统可理解的标准事件,并传递给后续模块。理解这一过程有助于深入掌握输入系统的工作原理,对优化输入响应速度、处理兼容性问题等场景具有重要意义