4. Android 输入系统架构:EventHub、InputReader、InputDispatcher 与触摸事件分发流程

这一章,我们来聊聊 Android 输入系统的骨架。说白了,就是一根手指从触摸屏点下去,到上层应用响应,中间到底经历了什么。

我刚开始接触 Android 驱动时,总觉得上层框架太抽象,不就是个中断上报吗?后来踩了坑才明白——驱动只是起点,真正的战场在框架层。触摸偏移的补偿,很多时候不是在驱动里改个坐标就完事的,你得理解 EventHub 怎么读数据、InputReader 怎么解析、InputDispatcher 怎么派发,才能精准定位问题。

核心认知:Android 输入系统是一个三层管道——内核驱动上报原始事件 → 框架层加工为结构化事件 → 窗口系统派发给目标应用。每一层都可能引入偏移,每一层也都有补偿机会。

4.1 整体架构:一张图看懂

先看整体流程。我习惯把 Android 输入系统画成下面这张图,每次排查触摸偏移问题时,我都会对着它捋一遍数据流。

Android 输入系统架构流程图 Android 输入系统架构流程图 Linux 内核驱动层 触摸屏驱动 / 输入子系统 EventHub 读取 /dev/input/eventX 原始事件 InputReader 解析、校准、生成 RawEvent InputDispatcher 派发 → 窗口系统 → 应用 💡 说明: EventHub 是 C++ 层,InputReader 和 InputDispatcher 运行在 InputManagerService 进程中。 触摸偏移的补偿点:驱动层(硬件坐标修正)、InputReader(框架层校准)、应用层(软件补偿)。

嗯,这张图我每次培训都会用。你看,从下往上四层,每一层都有它的职责。我们逐层拆解。

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 层的威力——不改驱动,不改应用,只改配置文件就能解决偏移。

注意:.idc 文件的配置只对 InputReader 生效。如果你的触摸偏移是硬件层面的非线性畸变(比如边缘弯曲),靠简单的 offset/scale 是搞不定的,必须在驱动里做查表校正。

4.4 InputDispatcher:事件派发的交通警察

InputDispatcher 拿到 InputReader 送来的事件后,开始做派发决策。它的核心逻辑是:

  1. 查找目标窗口:根据触摸点的坐标,找到当前焦点窗口或触摸区域对应的窗口。
  2. 事件排队:把事件放入目标窗口的输入队列。
  3. 唤醒应用线程:通过 Looper 机制通知应用处理事件。

这里有个容易踩坑的地方——窗口坐标变换。应用窗口可能做了缩放、旋转或者有状态栏偏移。InputDispatcher 在派发时,会把屏幕坐标转换成窗口坐标。如果这个转换逻辑有 bug,触摸点就会在应用里偏掉。

我曾经遇到一个 case:某款平板在横竖屏切换后,触摸点总是偏左上角。查了两天,最后发现是 InputDispatcher 在计算窗口变换矩阵时,没有考虑屏幕旋转后的宽高交换。说白了,就是 displayInfo.orientation 变了,但 inputWindowHandle 里的变换矩阵没更新。

层级 职责 偏移补偿方式 调试手段
EventHub 读取原始事件 无(仅透传) getevent -lt
InputReader 解析 + 校准 .idc 配置、坐标变换矩阵 dumpsys input
InputDispatcher 派发 + 窗口变换 窗口坐标转换 dumpsys window
应用层 接收并处理 软件补偿(onTouchEvent) MotionEvent 日志

4.5 触摸事件分发流程:从手指到应用

把上面三层串起来,就是完整的分发流程。我习惯用下面这个步骤来记忆:

  1. 硬件中断:触摸屏 IC 检测到触摸,通过 I2C/SPI 上报坐标数据。
  2. 内核驱动:生成 input_event,写入 /dev/input/eventX。
  3. EventHub:epoll 唤醒,read 出 RawEvent。
  4. InputReader:解析协议,应用校准,生成 NotifyArgs。
  5. InputDispatcher:查找目标窗口,转换坐标,派发事件。
  6. ViewRootImpl:应用主线程收到事件,分发给 View.dispatchTouchEvent()。
  7. 应用处理:onTouchEvent() 或 onInterceptTouchEvent() 响应。

你看,从第 1 步到第 7 步,每一步都可能引入偏移。但反过来,每一步也都可以做补偿。我个人建议的排查顺序是:先看 EventHub 的原始数据对不对 → 再看 InputReader 的校准参数是否生效 → 最后查 InputDispatcher 的窗口变换。按这个顺序走,90% 的触摸偏移问题都能定位。

实战建议:在调试触摸偏移时,用 adb shell dumpsys input 查看 InputReader 的校准参数和 InputDispatcher 的窗口信息。重点关注 TouchCalibration 和 WindowTransform 两个字段。如果它们和预期一致,那问题大概率在应用层。