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 的步骤,我总结为:

  1. 定位主线程:看它处于什么状态(Running、Blocked、Waiting)。
  2. 找阻塞原因:如果是 Blocked,找锁的持有者。如果是 Waiting,看它在等什么条件。
  3. 看 CPU 负载:如果 CPU 负载很高,可能是计算密集型任务占用了主线程。
  4. 检查系统服务:如果主线程在等系统服务,检查服务是否正常。

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 分析。

我分享一个我自己的排查方法:

  1. 收集多次 traces:间隔 1 秒收集一次,看线程状态是否变化。
  2. 找循环等待:线程 A 等锁 B,线程 B 等锁 C,线程 C 等锁 A。这就是死锁。
  3. 看锁的地址:同一个锁地址出现在多个线程的堆栈中,说明有竞争。

避坑指南:我曾经遇到一个死锁,只在特定场景下复现(导航+音乐+电话同时使用)。后来发现是三个服务之间互相调用了对方的同步方法。解决方案很简单:把同步方法改成异步调用,或者调整锁的顺序。

车载死锁的典型场景

场景 原因 解决方案
CarService 与 MediaService 互相调用 同步方法导致循环等待 改为异步回调,或使用 Handler
UI 线程等待硬件驱动 驱动响应慢,主线程阻塞 硬件操作放到子线程,超时处理
多进程共享锁 Binder 调用导致锁传递 减少跨进程同步,使用消息队列

SVG 图:ANR 分析流程

车载 ANR 分析流程 ANR 发生 收集 traces.txt 分析主线程状态 Blocked(等锁) Waiting(等条件) 找锁持有者 检查等待条件 修复并验证

总结

车载 ANR 分析,说白了就是一场“找凶手”的游戏。traces 是案发现场,Watchdog 是巡逻警察,死锁检测是法医鉴定。

我个人觉得,预防比排查更重要。写代码时注意:

  • 主线程不要做任何耗时操作
  • 锁的粒度要小,顺序要一致
  • 关键服务加上超时保护

做到这几点,ANR 会少很多。嗯,今天就聊到这里。

公众号:蓝海资料掘金营,微信deep3321