4. ANR trace文件解读:主线程堆栈、binder调用、锁等待、CPU负载
4.1 现象识别
导航操作触发 ANR 后,系统会生成 /data/anr/traces.txt(Android 10 以下)或 /data/anr/anr_* 压缩包(Android 10+)。从 trace 文件中可以直观看到 主线程 当前执行栈,通常表现为以下三种阻塞模式之一:
- Binder 调用阻塞:主线程正在等待一次 IPC 响应,栈顶可见
BinderProxy.transact()或Binder.execTransact()停顿。 - 锁等待(Lock contention):主线程尝试获取某个同步锁(monitor、ReadWriteLock、ReentrantLock)但被其他线程持有,栈顶可见
Object.wait()或LockSupport.park()及相关 Lock 实现类。 - CPU 竞争/调度延迟:主线程处于 Runnable 状态但长时间未获得 CPU 时间片,trace 中显示大量“running”时间,同时系统 CPU 负载较高。
在导航 ANR 场景中,Binder 调用阻塞 和 锁等待 是最常见的原因,CPU 负载高通常作为并发因素加剧问题。
4.2 数据采集
拿到 ANR trace 文件后,应提取以下关键数据段:
| 数据项 | 提取位置 | 示例 |
|---|---|---|
| 主线程堆栈 | "main" prio=5 tid=1 段落 | |
| Binder 调用栈 | 主线程或 Binder 线程栈中的 BinderProxy.transact() | |
| 锁等待详情 | 主线程后的 Blocked 或 Waiting 状态,以及 held by 提示 | |
| CPU 负载 | 文件末尾 CPU usage 段 | |
采集技巧:使用
adb bugreport 一次性获取 trace + logcat + CPU 信息;将 trace.txt 拖入 Chrome 的 Chrome Tracing(chrome://tracing)可可视化主线程时间线。
4.3 原因定位——故障树分析
根据 trace 中的现象组合,建立以下故障树,快速判别根因:
4.4 判断规则与排查表
根据 trace 状态字段迅速锁定类别:
| 主线程状态 | 栈顶特征 | 诊断结论 | 优先级 |
|---|---|---|---|
| Blocked | BinderProxy.transact() | Binder 同步调用阻塞,服务端可能慢或 Binder 线程池满 | 高 |
| Blocked | Object.wait() / LockSupport.park() | 锁等待,其他线程持有锁未释放 | 高 |
| Runnable | 无阻塞函数,但累计 running > 5000ms | CPU 调度延迟,需分析系统负载和线程优先级 | 中 |
| Waiting | Object.wait() + 无 held by | 条件等待,可能被 notify 延迟或死循环 | 中 |
定位口诀:
阻塞看锁链,运行看负载;Binder 查线程,死锁找循环。
阻塞看锁链,运行看负载;Binder 查线程,死锁找循环。
4.5 修复动作
三种典型场景的修复方向:
- Binder 调用阻塞:将导航相关的 Binder 调用改为异步(Oneway),或使用
AsyncTask/HandlerThread切换到后台线程;若必须同步,缩短服务端处理时间(如缓存线路数据)。 - 锁等待:使用
synchronized锁时减少临界区代码;避免在onDraw/onLayout等主线程回调中获取锁;改用ReadWriteLock或ConcurrentHashMap等无锁结构。 - CPU 竞争:减少主线程中的浮点计算、大量日志输出、不合理循环;将导航地图渲染的 CPU 密集操作移入 RenderThread 或使用 OpenGL。
// Binder 异步化示例(服务端 AIDL):
interface IRouteService {
// 改为 oneway 方法
oneway void fetchRouteAsync(in RouteRequest request);
}
4.6 复测
修复后使用以下方法验证:
- 模拟导航操作重复 50 次,观察
adb logcat |grep ANR不再出现主线程 ANR。 - 利用
StrictMode捕获主线程的 Binder 调用和锁持有时间:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
- 在 Dalvik Monitor 中检查锁竞争:
adb shell dumpsys window monitors应无长时间等待。
4.7 预防
- 开发阶段:启用 StrictMode 的
penaltyDeath()或penaltyLog()并集成到 CI。 - 发布前:在低端设备(例如 2GB RAM 机型)上跑导航自动化测试,收集 trace。
- 线上监控:接入
ANRWatchDog或 Android Vitals,ANR 发生时自动上传 trace 和 CPU 快照。