触摸事件传递机制:从屏幕到应用的完整链路
做触摸优化,第一件事就是搞清楚——你点下去的那一下,到底经历了什么才跑到 App 里?
我刚开始接触这块时,以为就是屏幕检测到手指,然后直接告诉应用。后来被一个 200ms 的延迟问题折磨了两周,才老老实实把整个链路啃了一遍。说白了,这中间经过的每一层,都可能成为延迟的帮凶。
硬件到应用的完整链路
一次触摸,从物理世界进入数字世界,大致要经过 5 个关卡:
- 屏幕(硬件层)——电容屏检测到手指触碰,产生电信号
- 驱动(内核态)——驱动将电信号转换成标准化的触摸数据包
- 内核(Linux Input 子系统)——将数据包上报到 input 事件队列
- 应用框架(Framework)——InputReader 读取事件,InputDispatcher 分发
- 应用层——View 树的事件分发与消费
关键认知:每一层都有缓冲区、队列、调度策略。任何一个环节出现拥塞,触摸响应就会变慢。我见过最离谱的案例,是驱动层上报频率被降到了 60Hz,结果触摸采样直接砍半。
下面这张图,是我每次培训必放的。它把整个链路画成了一目了然的流水线:
事件分发流程:InputReader → InputDispatcher → 应用
Framework 层是整个链路中最复杂、也最容易被忽视的部分。我把它拆成三个角色来讲:
| 角色 | 职责 | 运行线程 | 常见延迟原因 |
|---|---|---|---|
| InputReader | 从 /dev/input/ 读取原始事件,解析为 InputEvent | InputReader 线程 | 读取频率低、事件队列积压 |
| InputDispatcher | 将事件分发给目标窗口,处理策略(如触摸消抖) | InputDispatcher 线程 | 窗口查找耗时、ANR 超时机制 |
| 应用主线程 | 接收事件并执行 View.dispatchTouchEvent() | 主线程(UI 线程) | 主线程阻塞、布局复杂 |
InputReader 的工作细节
InputReader 本质上是一个轮询器。它不断从内核的 input 设备节点读取事件,然后做两件事:
- 事件解析:把原始坐标、压力、时间戳转成 MotionEvent
- 策略过滤:比如虚拟按键映射、边缘防误触
我的经验:有一次排查延迟,发现 InputReader 的轮询间隔被改成了 16ms(约 60Hz),但屏幕是 120Hz 的。这意味着每两次触摸采样之间,Reader 只读一次。后来把轮询间隔降到 4ms,延迟直接降了 8ms。这个参数在 Android 源码的 InputReader.cpp 里,叫 mEventHub->getNextEvent() 的超时时间。
InputDispatcher 的分发策略
InputDispatcher 拿到事件后,不会直接丢给应用。它会先做几件事:
- 查找目标窗口——根据触摸坐标找到对应的 Window
- 检查窗口状态——是否可接收事件、是否处于 ANR 倒计时
- 放入窗口的事件队列——每个窗口有自己的 InputEventQueue
- 通知应用取事件——通过管道或共享内存机制
这里有个坑:如果目标窗口正在处理上一个事件,Dispatcher 会等待。我曾经遇到一个案例,某个页面在 onTouchEvent 里做了大量计算,导致后续事件排队,触摸滑动明显卡顿。解决方案很简单——把计算移到子线程。
注意:InputDispatcher 有一个「超时机制」。如果应用在 5 秒内没有消费事件,系统会认为应用 ANR。但反过来,如果应用处理得太快,Dispatcher 也可能因为窗口切换而丢事件。嗯,这里要平衡好。
应用层的事件分发
事件到达应用层后,走的是 View 树的分发流程:
Activity.dispatchTouchEvent()
└─ ViewGroup.dispatchTouchEvent()
├─ onInterceptTouchEvent() // 是否拦截
└─ child.dispatchTouchEvent()
└─ View.onTouchEvent() // 最终消费
这个流程看似简单,但每个方法都可能成为延迟点:
- onInterceptTouchEvent 如果写得复杂,每次触摸都要遍历子 View
- ViewGroup 的触摸目标查找 如果层级深,遍历耗时线性增长
- onTouchEvent 回调 如果做了 I/O 或网络,直接卡死主线程
一句话总结:触摸延迟不是单一环节的问题。屏幕快了,驱动跟不上;驱动快了,Framework 分发不过来;Framework 优化了,应用主线程又卡住了。排查时一定要从链路视角去看,而不是只盯着应用层。
我个人习惯在排查延迟时,先抓一份 systrace / perfetto 看全局。从 InputReader 的读取时间戳,到应用层的 dispatchTouchEvent,每个环节的耗时一目了然。后面几章我会详细讲怎么用这些工具定位具体瓶颈。