26、稳定性与ANR:车载 ANR 分析、Watchdog 机制、死锁检测
车载系统最怕什么?我个人觉得,最怕的就是屏幕卡住不动。
你想想看,用户正在导航,突然界面无响应。几秒钟后,系统弹出“XX应用无响应”。这在手机上可能只是关掉重开,但在车上,这可能是安全隐患。所以,ANR(Application Not Responding)在车载领域,是绝对的高优问题。
车载 ANR 的特殊性
车载 ANR 和手机 ANR 本质一样,但触发条件更苛刻。
手机 ANR 通常是因为主线程做了耗时操作。车载呢?除了这个,还有几个“坑”:
- 系统服务超时:车载很多功能依赖系统服务(比如 CarService)。如果服务本身卡住,所有依赖它的 App 都会 ANR。
- 硬件交互阻塞:比如读取 CAN 总线数据时,如果硬件驱动没及时响应,主线程会一直等。
- 多屏交互:中控屏、仪表盘、HUD 共享进程。一个屏的 ANR 可能拖垮整个系统。
核心观点:车载 ANR 不只是 App 的问题,更是系统整体稳定性的体现。
ANR 分析实战
遇到 ANR,第一步做什么?看 traces.txt。
这个文件记录了 ANR 发生时所有线程的堆栈。我习惯先看主线程("main")在干什么。
// 典型的 ANR traces 片段
"main" prio=5 tid=1 Blocked
at com.android.car.xxx.CarService.onBind(CarService.java:120)
- waiting to lock <0x0c71f3a0> (a com.android.car.xxx.CarManagerService)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6543)
...
看到 Blocked 状态了吗?这说明主线程在等一把锁。谁拿着这把锁?往下翻,找持有锁的线程。
我的经验:我曾经遇到一个 ANR,主线程等锁,而持锁线程在等 Binder 调用返回。Binder 调用又等主线程释放锁。典型的死锁。这种问题,光看 traces 不够,还得结合 logcat 看时序。
分析 ANR 的步骤,我总结为:
- 定位主线程:看它处于什么状态(Running、Blocked、Waiting)。
- 找阻塞原因:如果是 Blocked,找锁的持有者。如果是 Waiting,看它在等什么条件。
- 看 CPU 负载:如果 CPU 负载很高,可能是计算密集型任务占用了主线程。
- 检查系统服务:如果主线程在等系统服务,检查服务是否正常。
Watchdog 机制
Watchdog 是 Android 系统用来检测死锁和长时间阻塞的机制。说白了,就是一个“看门狗”线程,定期检查关键线程是否还活着。
车载系统对 Watchdog 做了增强。为什么?因为车载场景下,有些服务不能重启(比如仪表盘显示)。一旦 Watchdog 判定服务挂了,系统可能直接重启整个进程,而不是等 ANR。
// Watchdog 的核心逻辑(简化版)
public class Watchdog extends Thread {
private static final long TIMEOUT = 30 * 1000; // 30秒超时
public void run() {
while (true) {
synchronized (this) {
wait(TIMEOUT);
}
// 检查关键线程
if (!isMonitorThreadAlive()) {
// 触发重启
rebootSystem();
}
}
}
}
Watchdog 检查哪些线程?
- 主线程(ActivityThread)
- Binder 线程池
- 关键系统服务线程(比如 CarService)
注意:Watchdog 超时时间不能设得太短。我见过有人改成 10 秒,结果系统频繁误重启。车载场景建议保持 30 秒以上,给系统足够的容错时间。
死锁检测
死锁是 ANR 的常见原因之一。四个必要条件:互斥、持有并等待、不可剥夺、循环等待。
Android 系统提供了死锁检测工具。最常用的是 dumpsys 命令:
adb shell dumpsys window | grep -i "deadlock"
adb shell dumpsys activity | grep -i "deadlock"
但说实话,这些命令只能检测到明显的死锁。更隐蔽的死锁,需要靠 traces 分析。
我分享一个我自己的排查方法:
- 收集多次 traces:间隔 1 秒收集一次,看线程状态是否变化。
- 找循环等待:线程 A 等锁 B,线程 B 等锁 C,线程 C 等锁 A。这就是死锁。
- 看锁的地址:同一个锁地址出现在多个线程的堆栈中,说明有竞争。
避坑指南:我曾经遇到一个死锁,只在特定场景下复现(导航+音乐+电话同时使用)。后来发现是三个服务之间互相调用了对方的同步方法。解决方案很简单:把同步方法改成异步调用,或者调整锁的顺序。
车载死锁的典型场景
| 场景 | 原因 | 解决方案 |
|---|---|---|
| CarService 与 MediaService 互相调用 | 同步方法导致循环等待 | 改为异步回调,或使用 Handler |
| UI 线程等待硬件驱动 | 驱动响应慢,主线程阻塞 | 硬件操作放到子线程,超时处理 |
| 多进程共享锁 | Binder 调用导致锁传递 | 减少跨进程同步,使用消息队列 |
SVG 图:ANR 分析流程
总结
车载 ANR 分析,说白了就是一场“找凶手”的游戏。traces 是案发现场,Watchdog 是巡逻警察,死锁检测是法医鉴定。
我个人觉得,预防比排查更重要。写代码时注意:
- 主线程不要做任何耗时操作
- 锁的粒度要小,顺序要一致
- 关键服务加上超时保护
做到这几点,ANR 会少很多。嗯,今天就聊到这里。