既然你选择了继续,那我们就顺着"屏幕亮起"后的自然逻辑------"手指触控",进入系统交互的最前线。
欢迎来到 第四阶段:指尖的舞蹈(Input 系统与 ANR) 👆。
如果说 SurfaceFlinger 是把画面"推"给用户,那 Input 系统就是把用户的意图"拉"回系统。对于系统开发来说,这也是 ANR (Application Not Responding) 的发源地。如果不搞懂 Input,你永远无法彻底解决"应用无响应"的问题。
🏛️ Part 1: 原理与流程 ------ 从硬件中断到 onClick
当你的手指触碰屏幕的那一微秒,一场跨越内核与用户空间的接力跑就开始了。
这套机制极其高效,因为触摸事件频率极高(普通屏幕 60Hz~120Hz,游戏手机甚至 360Hz+),如果处理慢了,就是"跟手性"差。
- 内核层 (Kernel):原始数据的产生
- 硬件中断: 触摸屏驱动(Touch Driver)感知电压变化,产生中断。
- 设备节点: 驱动将原始坐标 (X, Y, Pressure) 写入 /dev/input/eventX 文件。
- 工具验证: 你可以用 adb shell getevent -l 直接读取这些原始二进制流。如果你能看到数据哗哗滚屏但屏幕没反应,那就是上层软件的问题;如果这里没数据,就是硬件或驱动的问题。
- Native 层 (SystemServer):InputManagerService (IMS)
这是处理 Input 的核心,它并不在 Java 层跑,而是主要在 C++ 层(为了快)。它主要有两个线程在死循环狂奔:
- InputReader 线程:
- 通过 EventHub 利用 epoll 机制监听 /dev/input/ 下的所有设备。
- 负责把原始协议(Protocol A/B)转换成 Android 标准的 KeyEvent 或 MotionEvent。
- InputDispatcher 线程:
- 它持有当前的 "焦点窗口" (Focused Window) 信息(来自 WindowManagerService)。
- 它的任务是把事件派发给当前最顶层的那个窗口。
- 传输通道:SocketPair (关键考点) ⚡
注意!这里不走 Binder!
Binder 虽然只有一次拷贝,但对于每秒几百次的高频触摸事件来说,开销还是太大了。
- 机制: Android 使用 SocketPair (Unix Domain Socket) 来传输 Input 事件。
- 原理: InputDispatcher 持有一个 Socket 的发送端,App 的主线程 (ViewRootImpl) 持有接收端(即 InputChannel)。这是一条双向的、全双工的专属管道。
🐢 Part 2: 噩梦的根源 ------ Input ANR 是怎么发生的?
很多人以为 ANR 是"主线程卡了"。这只说对了一半。Input ANR 的本质是"派发超时"。
让我们看看 InputDispatcher 是怎么判定的: - 派发 (Dispatch): InputDispatcher 试图往目标窗口的 Socket 里写入事件。
- 堵塞 (Block): 如果 App 的主线程正忙(比如在 onCreate 里读文件),它就没空去 Socket 里读数据。Socket 的缓冲区(通常只有几 KB)瞬间被填满。
- 等待 (Wait): InputDispatcher 发现 Socket 满了写不进去,它不会立刻报错,而是记录下当前时间,然后去睡一会(等待下一次唤醒)。
- 引爆 (Timeout): 当下一个事件到来时,或者系统定时检查时,InputDispatcher 发现:"咦,我 5 秒前发给这个窗口的事件,它到现在还没处理完(Socket 还是满的,或者没有这就收到 Finish 信号)?"
- BOOM! 触发 Input dispatching timed out -> 弹出 ANR 对话框。
深刻理解: 导致 Input ANR 的原因,往往不是当前这个触摸事件,而是上一个事件处理得太慢,堵住了管道。
🛠️ Part 3: 实战分析 ------ 谁偷走了我的触摸?
作为系统开发,遇到"断触"、"触摸无效"或"ANR",我们怎么查?
- BOOM! 触发 Input dispatching timed out -> 弹出 ANR 对话框。
- 焦点排查 (Focus Debugging) 🔍
很多时候屏幕没反应,不是因为卡了,而是因为焦点乱了。比如一个透明的、只有 1 像素的悬浮窗抢走了焦点。
-
命令:
adb shell dumpsys window | grep -i "mCurrentFocus"
-
分析: 看看 mCurrentFocus 到底是谁。如果是一个你没见过的 Window,那就是它挡住了你的点击。
- ANR Trace 分析 (traces.txt) 📜
当发生 ANR 时,系统会生成 /data/anr/traces.txt。
- 找主线程 (main): 搜索 Identify: main。
- 看状态:
- 如果是 Native (epoll_wait) 或 Runnable 但堆栈在 MessageQueue.next(),说明主线程其实是闲的!这可能是SystemServer 卡住了,导致 Input 事件发不过来。
- 如果是 Suspended 或 Blocked,看持有锁的是谁。
- 如果堆栈停在 Bitmap.createBitmap 或 DB 操作上,那就是实锤的主线程耗时。
- "幽灵触摸" (Ghost Touch) 👻
有时候手机会自动乱点。
- 手段: 打开开发者选项里的 "指针位置" (Pointer Location)。
- 分析: 观察顶部的坐标栏。
- 如果你没手碰屏幕,坐标还在跳,那就是硬件/TP 固件的问题(比如充电器干扰、屏幕静电)。
- 如果是多点触控(Multi-touch)错乱,通常是驱动层对 Protocol B 的 Slot 分配有问题。
💡 一个关于 Input 的思维陷阱
在系统定制中,我们经常遇到这样的需求:"我想做一个全局手势,比如三指下滑截屏"。
初级做法: 在 App 层写个 Service 监听。 (太慢,且无法覆盖锁屏界面)
高级做法: 改 InputDispatcher.cpp 或 PhoneWindowManager.java。
挑战:
你需要拦截事件,但又不能阻塞后续的正常派发。一旦你在 InputDispatcher 的锁里加了耗时逻辑(比如存 Log),整个系统的触摸都会变卡,甚至导致所有 App 发生 ANR。
🎯 下一步?
现在我们已经把"手"的问题解决了。数据已经安全抵达了 App 的主线程。
App 的主线程拿到 Input 事件后,会扔给 Handler 进行处理。
这就引出了 Android 系统最经典、最精妙的 "消息驱动模型"。没有它,Android 甚至无法呼吸。
接下来的 第五阶段,我们要不要深入 Looper、MessageQueue 和那个神秘的 epoll_wait?去看看主线程到底是怎么做到"死循环"却不消耗 CPU 的? 🔄