ANR 日志分析:traces.txt、logcat、bugreport 的解读方法
说实话,每次看到网上有人问“ANR 怎么排查”,我第一反应都是:你把日志拿出来看看?但很多人根本不知道要看哪几个文件,更别提怎么读这些日志了。我自己刚带团队那会儿也踩过不少坑——明明 traces.txt 里写得清清楚楚,偏要去 logcat 里瞎翻半天。
这一章,我就把三种最关键的日志文件怎么用、怎么看、怎么串起来,一次性说清楚。
traces.txt —— 你的第一把手术刀
traces.txt 是 Android 系统在 ANR 发生时自动 dump 出来的线程堆栈。说白了,就是系统拍了一张现场照片,上面清清楚楚地标着每个线程在“案发”那一刻在干什么。
去哪找?一般有两个路径:
- /data/anr/traces.txt(需要 root 或者 adb pull)
- /data/anr/traces_xxx.txt(多进程时命名带 pid)
我个人习惯先看 main thread(主线程)的堆栈。你想想看,ANR 不就是因为主线程堵了才发生的吗?所以第一眼要找到类似这样的内容:
----- pid 12345 at 2025-05-10 14:30:00 -----
Cmd line: com.example.myapp
...
"main" prio=5 tid=1 Blocked
at java.lang.Object.wait(Native Method)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:179)
at com.example.data.local.dao.UserDao_Impl$1.call(UserDao_Impl.java:42)
- locked <0x12345678> (a java.lang.Object)
at io.reactivex.internal.operators.observable.ObservableCreate.subscribe(ObservableCreate.java:71)
...
注意看 Blocked 状态,还有 locked 的那个对象。这就是典型的死锁或锁竞争。我有一次排查一个相册 App 的 ANR,用户翻相册时偶尔卡 10 秒,一看 traces.txt 里 SQLiteDatabase.lock() 被持有了快 8 秒——后来发现是某次 Room 事务没关闭游标,导致后续查询全堵了。
logcat —— 时间线上的上下文
traces.txt 只是瞬间快照,但 ANR 往往不是“咔嚓”一声就发生的。logcat 能帮你追溯前因。
关键搜索词:
ANR in com.example.myapp—— 系统通知 ANR 的入口DALVIK THREADS或ART THREADS—— 系统定期 dump 的线程信息WaitForAsyncResult/Input dispatching timed out—— 输入事件超时ContentProvider相关的超时
举个例子,有一次线上反馈 App 启动闪白屏很久。我在 logcat 里搜 ANR 没搜到,但看到了很多:
W/ActivityManager: Force finishing activity com.example/.MainActivity
I/WindowManager: Input event dispatching timed out sending to com.example/.MainActivity
嗯,这里要注意:Input dispatching timed out 说明用户触摸事件在 5 秒内没有被消费。顺着时间戳往前查,发现 RoomDatabase.beginTransaction 在 onCreate 里执行了一个 3 秒的批量插入。你说巧不巧,主线程直接堵死了。
用 logcat 分析时,我建议把时间窗口拉宽到 ANR 日志之前的 10~30 秒,你往往能发现“犯罪线索”。
# 过滤 ANR 相关,带时间和进程 tag
adb logcat -b main -b events -b system -v time | grep -E "ANR|Input dispatching|WindowManager|ActivityManager"
这样能一次性把事件、系统、主日志合并过滤,减少重复翻屏。
bugreport —— 完整体检报告
bugreport 是 Android 系统提供的“全量快照”,包含了 traces.txt、logcat、系统服务 dump、CPU 使用率、内存状态、wake lock 等等。通常在正式发版前的稳定性测试里,PM 会要求提供 bugreport。
怎么抓?
adb bugreport bugreport.zip
# 或
adb bugreport > bugreport.txt # 不推荐,太大
我个人习惯拿到 bugreport 后先看这几个部分:
- DUMP OF SERVICE activity —— 当前所有 Activity 的状态、等待的 broadcast、服务记录
- CPU 使用情况 —— 在
CPU info段里,看看是不是有进程吃满核导致调度延迟 - PROCESSES —— 各个进程的优先级、oom_adj 值,ANR 进程通常会被标记为
F(foreground) - VM TRACES —— 这就是 traces.txt 的内容,但可能因为文件大被截断
曾经有一次线上 ANR,traces.txt 里显示主线程在做 SharedPreferences.getString(),但我用 bugreport 查看了 DUMP OF SERVICE diskstats,发现系统 I/O 一直处于 100% 忙碌状态——原来是另一个进程在疯狂读写数据库导致闪存排队。你说这锅该谁背?没 bugreport 根本想不到是 I/O 瓶颈。
zipinfo 看看大小,然后直接用命令行 sed 或 grep 提取关键段,不展开整个文件。
三个文件的对比总结表格
| 日志文件 | 核心价值 | 抓取时机 | 最大坑点 |
|---|---|---|---|
| traces.txt | 拍下 ANR 瞬间的所有线程堆栈 | ANR 发生时自动生成 | 可能被多个 ANR 覆盖,老日志会被删除 |
| logcat | 时间线上追踪系统事件和 App 行为 | 可实时 / 可历史回溯 | 缓冲区可能被冲掉,需要提前设置大小 |
| bugreport | 全系统级快照,包含 CPU、内存、磁盘 | 手动触发,适合基准对比 | 文件极大,分析慢;部分版本信息不完整 |
分析流程总览
下面这张图总结了我自己平时排查 ANR 时“先看哪个、再看哪个”的套路。每次遇到 ANR,别急着翻代码,先把日志对齐。
实际分析时,我一般先打开 traces.txt,找到主线程堆栈,判断是死锁、数据库慢查询还是反射耗时。如果是死锁,logcat 和 bugreport 里往往会有 held by thread X 的线索。如果是 I/O 问题,bugreport 里的 CPU 和磁盘情况能一锤定音。
你想想看,遇到 ANR 第一件事不是改代码,而是把这三个文件摆在一起,就像侦探把照片、监控录像和通话记录铺在桌上一样。嗯,这样才不容易漏线索。