4. ANR日志分析入门
4.1 为什么必须掌握ANR日志
ANR日志是定位主线程卡顿、死锁、Binder超时最直接的证据链。traces.txt 文件记录了ANR发生瞬间所有线程的堆栈快照,是问题诊断的“第一现场”。本章将拆解该文件的三大核心模块:Main Thread Stack(主线程堆栈)、Binder Transaction Dump(Binder事务转储)及全局锁/等待链。
工程底线: 任何线上ANR修复都必须先验证traces.txt中的堆栈与修复方案吻合,否则可能引入新问题。
4.2 traces.txt的结构与定位
Android系统在检测到ANR后会立即dump所有线程堆栈到 /data/anr/traces.txt。其核心结构如下:
| 区域 | 关键内容 | 排查价值 |
|---|---|---|
| Header | ANR时间、进程名、pid、系统负载 | 确定问题发生的精确时间窗口 |
| Main Thread | “main”线程完整堆栈 | 直接定位UI卡顿位置 |
| Binder Threads | “Binder:xxx_1/2/3”堆栈 | 找出远程调用堵塞点 |
| Other Threads | AsyncTask、HandlerThread等 | 确认其他线程是否死锁 |
| Binder Transaction Dump | 待处理/正在处理的事务列表 | 识别跨进程调用超时 |
4.3 解读Main Thread Stack——死锁与主线程阻塞
主线程堆栈是排查ANR的第一目标。常见模式及其解读:
| 堆栈特征 | 典型场景 | 解决方向 |
|---|---|---|
android.os.MessageQueue.nativePollOnce | Idle无任务,非ANR原因 | 忽略,继续看调用链上下文 |
InputMethodManager.waitForBind | 输入法死锁 | 检查输入法绑定流程 |
BinderProxy.transact + Lock | 主线程发起Binder调用并持锁 | 异步化Binder调用或避免主线程持锁 |
ViewRootImpl.dispatchWindowShown | 布局渲染超时 | 优化布局复杂度和异步inflate |
MessageQueue.next + Thread.sleep | 主线程主动sleep | 严禁主线程sleep,改用Handler延时 |
以下是一个实际的主线程堆栈示例(可以在traces.txt中看到类似内容):
"main" prio=5 tid=1 Native
#00 pc ... /system/lib64/libc.so (syscall+?)
#01 pc ... /system/lib64/libc.so (__pthread_cond_timedwait+?)
#02 pc ... /system/framework/arm64/boot-framework.oat (android.os.MessageQueue.nativePollOnce)
#03 pc ... /system/framework/arm64/boot-framework.oat (android.os.MessageQueue.next)
#04 pc ... /system/framework/arm64/boot-framework.oat (android.os.Looper.loop)
#05 pc ... /data/app/xxx/oat/arm64/base.odex (com.example.MyActivity.onCreate)
--- 持有锁: <0x0a1b2c3d> (a java.lang.Object)
关键信号: 主线程堆栈最底部显示
持有锁: <0x0a1b2c3d>,说明主线程正在等待某个锁释放,而该锁极有可能被另一个线程长时间占用。4.4 解读Binder Transaction Dump——跨进程超时
Binder Transaction Dump位于traces.txt末尾,用于诊断跨进程调用导致的ANR。核心字段分析:
| 字段 | 含义 | 异常判断 |
|---|---|---|
outstanding transactions | 未完成的事务数量 | >0 表示存在堵塞 |
proc pid | 正在处理的Binder服务端pid | 定位具体服务进程 |
code | Binder方法标识符 | 通过AIDL接口文档反向定位方法 |
waiting for | 等待的线程标识 | 出现“main”说明主线程被堵塞 |
典型Binder死锁场景示例:
Binder Transaction Dump:
outstanding transactions: 2
proc 1250: com.android.systemui
code: 0x12 (IBinder.INTERFACE_TRANSACTION)
waiting for: "main" tid=1
proc 1250: com.android.systemui
code: 0x34 (IXxxService.getData)
waiting for: "Binder:1250_2" tid=15
--- 持有锁: <0xdeadbeef>
死锁判定: 如果主线程(tid=1)在等待Binder线程B,而B又在等待主线程持有的锁,构成闭环死锁。必须打破持有锁的顺序或缩短持锁耗时。
4.5 ANR日志分析决策流程图
4.6 实战分析步骤
- 获取traces.txt:通过
adb pull /data/anr/traces.txt或Bugly/友盟等平台直接下载。 - 快速定位主线程:搜索
"main" prio=5 tid=1,复制其完整堆栈到分析工具。 - 识别阻塞原因:若堆栈底部出现
持有锁标识,记录锁地址(如0x0a1b2c3d)。 - 查找锁占用线程:在traces.txt中搜索该锁地址,找到持有该锁且堆栈显示非阻塞的线程。
- 验证Binder超时:若主线程回调包含
BinderProxy.transact,则切至Binder Transaction Dump区域,检查对应code是否长时间未返回。 - 修复方向选择:死锁 -> 调整锁顺序或减少锁粒度;Binder超时 -> 异步化调用或缩短服务端处理时间。
4.7 常见错误与最佳实践
❌ 常见错误:
- 只读主线程堆栈末尾,忽略
持有锁信息,导致漏掉死锁。 - 看到
MessageQueue.nativePollOnce就认为是ANR原因,实际需要结合前面的MessageQueue.next前一条调用。 - Binder事务dump只看
outstanding transactions数量,不分析具体等待链。
✅ 最佳实践:
- 使用脚本提取
持有锁和waiting for关键词,自动化生成锁等待图。 - 在traces.txt中搜索所有
tid=1和Binder:线程的堆栈,合并分析。 - 定期对线上ANR日志做聚类分析,识别重复出现的锁地址或Binder code。