1. Input系统整体架构:从触摸屏到应用层的完整链路

大家好,欢迎来到《Input系统事件分发源码深度剖析》的第一章。

今天我们来聊聊Android Input系统的整体架构。说白了,就是你的手指在屏幕上点了一下,这个动作是怎么一步步传到App里的某个按钮上的。这条链路很长,但我会尽量讲得通俗些。

我个人习惯把Input系统想象成一条流水线。触摸屏是原材料入口,应用层是成品出口。中间有几个关键的加工车间。嗯,咱们今天就先看看这条流水线长什么样。

1.1 整体链路概览

先画个简单的图在脑子里:

触摸屏硬件 → 内核驱动 → EventHub → InputReader → InputDispatcher → 窗口 → 应用层

这条链路上,最核心的两个角色就是 InputReaderInputDispatcher。它们都运行在系统进程 system_server 中,属于同一个线程——InputReader线程和InputDispatcher线程。

你可能会问:为什么要把它们分开?

我刚开始研究这部分源码时也有这个疑问。后来在项目中遇到过一次触摸卡顿的问题,排查下来才发现,如果让Reader和Dispatcher合在一起,读取事件时如果分发阻塞,整个系统都会卡住。所以Google把它们拆成了两个独立线程,各司其职。

1.2 InputReader:从硬件到事件的翻译官

InputReader的职责很明确:从内核读取原始事件,解析成Android能理解的事件结构

它通过EventHub与内核驱动交互。EventHub会监听 /dev/input/ 目录下的设备节点。每当有触摸事件产生,内核通过 read() 系统调用把原始数据传上来。

核心流程:

  1. EventHub 通过 epoll 监听输入设备
  2. InputReader 循环调用 EventHub::getEvents() 获取原始事件
  3. 将原始事件解析为 RawEvent 结构
  4. 通过 InputMapper 转换成 NotifyArgs
  5. 放入 InputDispatcher 的输入队列

这里有个关键点:InputMapper。不同的输入设备有不同的Mapper。触摸屏用 TouchInputMapper,键盘用 KeyboardInputMapper。我曾经在适配一款国产触摸屏时,发现上报的坐标总是偏移。排查到最后,是Mapper里的参数配置不对。嗯,这种坑踩过一次就记住了。

1.3 InputDispatcher:事件的路由与分发

InputDispatcher 是真正的"调度员"。它从InputReader那里拿到事件后,要做几件事:

  • 策略查询:询问WindowManagerService,当前焦点窗口是谁
  • 事件拦截:判断是否需要拦截(比如正在动画中)
  • 超时监控:如果App处理事件超时,会触发ANR
  • 有序分发:保证事件按顺序到达目标窗口

我建议你记住这个数据结构:QueuedInputEntry。Dispatcher内部维护了一个队列,所有待分发的事件都在里面排队。为什么要有队列?因为App处理事件是异步的,如果App还没处理完上一个事件,新事件就得等着。

避坑指南:

我曾经遇到过一个线上问题:某个页面滑动时偶尔会"跳帧"。查了半天,发现是InputDispatcher的队列里积压了太多事件。原因是App主线程在做耗时操作,导致事件处理不过来。解决方案很简单——把耗时操作移到子线程。但排查过程很痛苦,因为你要从Input系统一路追到App层。

1.4 InputReader与InputDispatcher的分工对比

维度 InputReader InputDispatcher
职责 读取原始事件,解析成标准格式 将事件分发给目标窗口
输入 内核驱动的原始数据 InputReader处理后的NotifyArgs
输出 放入Dispatcher的输入队列 发送到App的ViewRootImpl
线程 InputReader线程 InputDispatcher线程
关键类 EventHub, InputMapper InputChannel, Connection
常见问题 设备节点读取失败、Mapper配置错误 ANR、事件丢失、焦点错乱

你看这个表格就清楚了。Reader负责"翻译",Dispatcher负责"送信"。两者通过一个共享的 Queue 进行通信。这个队列是线程安全的,内部用了锁机制。

1.5 从触摸屏到应用层的完整流程

咱们走一遍完整的流程,你感受一下:

  1. 硬件层:手指触摸屏幕,电容变化产生电信号
  2. 内核驱动:驱动解析电信号,生成 input_event 结构,写入 /dev/input/eventX
  3. EventHub:通过epoll监听到fd可读,调用 read() 读取原始数据
  4. InputReader:将原始数据封装成 RawEvent,通过 TouchInputMapper 处理坐标、压力等信息
  5. InputDispatcher:从队列取出事件,查询焦点窗口,通过 InputChannel 发送
  6. ViewRootImpl:接收事件,通过 View.dispatchTouchEvent() 开始分发
  7. 应用层:事件到达具体的View,触发 onTouchEvent()

注意:

整个流程中,从第1步到第5步都是在系统进程完成的。只有第6步才进入App进程。这意味着,如果系统进程卡顿,所有App的触摸事件都会受影响。我记得有一次系统升级后,所有App都出现触摸延迟,最后定位到是InputReader线程里加了一个不必要的日志打印。

1.6 小结

这一章我们梳理了Input系统的整体架构。核心就两个角色:

  • InputReader:负责从硬件读取并解析事件
  • InputDispatcher:负责将事件分发给目标窗口

下一章我们会深入InputReader的源码,看看它到底是怎么从内核读取事件的。到时候会涉及到epoll、EventHub的细节,还有那个让我踩过坑的InputMapper配置。

嗯,今天就到这里。记住这条链路,后面的章节都会围绕它展开。