3、冻屏问题根因分析:Input系统阻塞、VSYNC信号异常、App ANR 关联
冻屏,说白了就是手机突然“僵住”了。屏幕画面卡死,点哪都没反应。用户的第一反应往往是“死机了”。但作为工程师,我们得知道——冻屏和黑屏不一样,屏幕还亮着,只是系统不响应了。
我个人习惯把冻屏问题分成三类来排查:Input系统阻塞、VSYNC信号异常、App ANR关联。这三者经常纠缠在一起,但根因往往只有一个。
3.1 Input系统阻塞——触控事件的“交通堵塞”
先说说Input系统。Android的触摸事件从硬件到应用,要经过一个很长的链路:
触摸屏 → 驱动 → InputReader → InputDispatcher → 目标窗口 → App
任何一个环节卡住,都会导致冻屏。我在项目中遇到过最典型的情况——InputDispatcher的“饥饿”问题。
为什么会这样?
InputDispatcher内部有一个等待队列。当App的主线程被长时间占用(比如在做GC或者布局计算),InputDispatcher会等待App处理完上一个事件,才能发送下一个。如果App一直不响应,InputDispatcher就会超时,然后抛出ANR。
但冻屏往往发生在ANR之前。嗯,这里要注意:
我曾经排查过一个案例:某款手机在低电量时频繁冻屏。抓取systrace后发现,InputReader线程被频繁block住,原因是触摸屏驱动的中断处理耗时过长。驱动在低电量模式下进入了某种“省电轮询”状态,导致事件上报延迟。
排查Input阻塞,我建议重点关注这几个地方:
- dumpsys input:查看InputDispatcher的等待队列长度
- systrace中的InputReader/InputDispatcher:看是否有长时间不调度
- logcat中的“Input event delivery timed out”:这是最直接的信号
3.2 VSYNC信号异常——画面更新的“心跳”停了
VSYNC是Android渲染的节拍器。SurfaceFlinger和App都依赖VSYNC信号来同步渲染。如果VSYNC信号异常,画面就会停止更新——这就是冻屏的另一种表现。
VSYNC信号的来源有两个:
| 信号源 | 说明 | 常见问题 |
|---|---|---|
| 硬件VSYNC | 由显示控制器(Display Controller)产生 | 驱动bug、帧率切换时信号丢失 |
| 软件VSYNC | 由SurfaceFlinger的EventThread模拟 | 线程优先级被抢占、CPU负载过高 |
我记得有一次排查一个冻屏问题,现象很诡异:手机在滑动桌面时突然卡住,但过几秒又自己恢复了。抓取trace后发现,SurfaceFlinger的EventThread在某个时刻停止了发送VSYNC信号。
根因是什么?
是某个第三方App注册了一个Choreographer.FrameCallback,但回调里做了耗时操作(网络请求)。这个回调运行在EventThread的同一个线程上,导致EventThread被阻塞。说白了,就是“一个老鼠屎坏了一锅粥”。
3.3 App ANR关联——冻屏的“前奏曲”
冻屏和ANR的关系,就像感冒和发烧。冻屏往往是ANR的前兆,但并不是所有冻屏都会演变成ANR。
从系统角度看,ANR的触发条件很明确:
- Input事件5秒内未处理
- BroadcastReceiver 10秒内未处理
- Service 20秒内未处理
但冻屏的触发条件更模糊。用户觉得“卡住了”,可能只是某个帧的渲染超过了16ms,导致画面停顿。这种停顿如果持续几秒,用户就会认为是冻屏。
我个人的经验是:冻屏问题中,有60%以上最终会触发ANR。所以排查冻屏时,一定要同时看ANR日志。
举个例子:
// 典型的ANR日志片段
ANR in com.example.app (com.example.app/.MainActivity)
Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago)
Load: 8.5 / 6.2 / 4.1
CPU usage from 0ms to 5000ms:
100% 12345/com.example.app: 99% user + 1% kernel
// 主线程堆栈
"main" prio=5 tid=1 Blocked
at com.example.app.MainActivity.onClick(MainActivity.java:45)
- waiting to lock <0x0c4d> (a java.lang.Object)
at com.example.app.SyncManager.syncData(SyncManager.java:120)
- locked <0x0c4d> (a java.lang.Object)
看到没?主线程在等待一个锁,而这个锁被另一个线程持有。这就是典型的死锁导致的冻屏。用户点击后,Input事件无法被处理,画面卡死。5秒后,系统弹出ANR对话框。
3.4 三者之间的关联与排查思路
这三者不是孤立的。我画个简单的关联图:
App主线程阻塞
↓
Input事件无法分发 → 用户触摸无响应 → 冻屏
↓
VSYNC信号无法消费 → 画面不更新 → 冻屏
↓
5秒超时 → ANR弹出 → 用户看到“应用无响应”
所以排查冻屏时,我的思路是这样的:
- 先看ANR日志:如果有ANR,直接定位主线程堆栈
- 再看systrace:看InputDispatcher和SurfaceFlinger的线程状态
- 最后看dumpsys:确认Input队列和VSYNC状态
我曾经遇到一个案例,冻屏持续了3秒就恢复了,没有触发ANR。抓trace后发现,是CPU频率被限制导致渲染超时。系统在高温时降频,但降得太猛,导致App无法在16ms内完成渲染。画面卡了几帧,用户就觉得冻屏了。
这种问题,说白了就是温控策略太激进。调整一下CPU调频参数就解决了。
总结一下:冻屏的根因分析,核心是找到“谁阻塞了主线程”。Input系统阻塞、VSYNC信号异常、App ANR,这三者只是表象。真正的根因,往往藏在主线程的堆栈里,或者系统服务的调度策略里。