4. ANR日志获取与初步分析
做ANR监控,第一步就是拿到日志。没有日志,一切分析都是空谈。我自己刚开始处理ANR时,经常在logcat里翻半天找不到关键信息,后来才明白——日志抓取的时间窗口和类型选择特别重要。
4.1 logcat抓取:锁定时间窗口
logcat是默认监控工具,但默认输出级别是V,信息太杂。我个人习惯用下面这条命令,专门过滤ANR相关标签:
adb logcat -v threadtime -b main -b system -b events *:I 2>&1 | grep -E "ANR|am_anr|ActivityManager|WatchDog|input|broadcast" > anr_log.txt
解释一下:-v threadtime带上线程ID和时间戳,方便后续对齐。-b main -b system -b events三个buffer基本覆盖了应用、系统和事件日志。为什么用*:I而不是V?Info级别以上足够,V日志太多,反而干扰我们找关键行。
我曾在线上环境遇到过logcat被压爆的问题——app反复写日志,导致logcat缓冲区溢出,ANR发生时重要日志被冲掉了。从那以后,我建议大家在测试时单独拉一个窗口持续输出:
adb logcat -b events -s am_anr:I > /tmp/am_anr.log
这个命令只保留系统事件中ANR相关的单行日志,体积小,适合长时间抓取。
date && adb logcat ...,或者直接用 adb logcat -v time,方便后续与bugreport、traces.txt做时间对齐。
4.2 traces.txt文件定位:系统最原始的“尸检”报告
traces.txt是系统在检测到ANR时,自动生成的线程堆栈dump文件。位置通常是 /data/anr/traces.txt,但不同厂商可能有变化。拿到root权限后直接pull:
adb shell su -c "cat /data/anr/traces.txt" > traces.txt
如果没有root,可以在app的 /data/data/包名/files/ 目录下找系统拷贝(Android 10+支持)。我个人习惯在ANR发生后的30秒内马上pull,因为系统可能后续会覆盖。
打开traces.txt,你会看到类似这种结构:
----- pid 12345 at 2025-02-20 14:30:00 -----
Cmd line: com.example.myapp
"main" prio=5 tid=1 Blocked
at com.example.MyClass.doSomething(MyClass.java:100)
- waiting to lock <0x1234> (a java.lang.Object)
at com.example.MyClass$1.run(MyClass.java:50)
...
关键信息:进程名、线程名、状态(Blocked/Runnable/Sleeping)、锁等待关系。我也经常用 grep -n "tid=" traces.txt 快速数一下有多少线程在Blocked状态。
4.3 dropbox收集:系统级的ANR“快照”
dropbox是Android系统的一个持久化存储,专门保存崩溃、ANR、watchdog等信息。比traces.txt更全面,包含进程状态、CPU使用率、内存快照等。获取方式:
adb shell dumpsys dropbox --print data_app_anr 2>&1 | head -100
或者先查看有哪些ANR记录:
adb shell dumpsys dropbox --list | grep -E "data_app_anr|system_app_anr"
dropbox里的内容很大,我通常关注几个字段:“Process:”后面的进程名、“CPU usage:”后面的总体CPU、“Load:”后面的系统负载、“DALVIK THREADS:”下面的主线程堆栈。这些信息能帮你判断是CPU饥饿还是死锁。
| 字段 | 含义 | 快速判断 |
|---|---|---|
| CPU usage | 系统总体CPU占用率 | >90% 通常CPU饥饿 |
| Load average | 系统负载(1/5/15分钟) | 负载 > CPU核数*2 则过载 |
| DALVIK THREADS | 虚拟机线程堆栈 | 查看主线程是否在等锁 |
| BINDER TRANSACTIONS | Binder调用情况 | 大量等待代表Binder阻塞 |
我建议你把dropbox里的CPU usage和进程的线程数做对比——如果线程数暴涨(比如超过200),那大概率是线程池泄漏导致CPU频繁切换,引发ANR。
4.4 bugreport生成:全景式诊断报告
bugreport是系统级诊断命令,生成一个zip压缩包,包含所有系统状态、日志、dump文件。生成命令:
adb bugreport bugreport_anr_20250220.zip
解压后,里面有个 bugreport-版本-时间.txt 主文件,内容很长(几十兆)。我只会快速扫关键的几段:
- “SYSTEM LOG” → 定位ANR发生时间戳
- “DUMPSYS MEMINFO” → 检查app内存是否接近阈值
- “DUMPSYS CPUINFO” → 看每个进程的CPU占用
- “ANR TRACES” → 直接嵌入的traces.txt内容
注意:bugreport生成大约需要5~20秒,所以它适合事后分析,不适合实时抓取。我在一次线上事故中,先用bugreport拿到了全貌,然后用traces.txt和logcat交叉验证,才定位到是第三方SDK导致主线程反复post延迟消息。
dumpsys dropbox --print data_app_anr 拿到近期ANR摘要,再从bugreport里找对应的详细数据,效率最高。
4.5 关键信息筛选:从海量日志中提炼核心
拿到日志后,不要从头看到尾。我一般按这个流程筛:
- 确定精确时间:在logcat中搜索“ANR in”或“am_anr”,找到
[ANR in com.example.myapp]行,记录时间戳。 - 定位主线程堆栈:在traces.txt里找该进程的“main”线程,看堆栈最后几行。如果状态是Blocked,记下锁地址。
- 查锁关系:搜索同一个锁地址在其他线程中的等待或持有情况。例如
<0x1234>出现在两个线程,说明死锁。 - 检查CPU和内存:从dropbox或bugreport中提取CPU usage、memory info。如果CPU>90%且主线程状态是Runnable,那就是计算密集型任务导致。
- 确认触发条件:倒推30秒内的logcat,看是否有网络请求、文件读写、数据库操作等耗时任务。
我遇到过最坑的一种情况:主线程看起来在等锁,但锁被一个Binder线程持有,而Binder线程又在等主线程的callback——其实是交叉死锁。这时就要把两个线程的堆栈都贴出来,逐行分析。
总结一下这张图的核心思想:四种日志来源各有侧重,但最终要汇合到时间对齐和筛选。我个人习惯先用logcat快速确认时间点,再用traces.txt看主线程堆栈,最后从dropbox或bugreport里补充CPU和系统状态。三者结合,大部分ANR都能在10分钟内定位。