ANR 日志分析:traces.txt、logcat、bugreport 的解读方法

说实话,每次看到网上有人问“ANR 怎么排查”,我第一反应都是:你把日志拿出来看看?但很多人根本不知道要看哪几个文件,更别提怎么读这些日志了。我自己刚带团队那会儿也踩过不少坑——明明 traces.txt 里写得清清楚楚,偏要去 logcat 里瞎翻半天。

这一章,我就把三种最关键的日志文件怎么用、怎么看、怎么串起来,一次性说清楚。

先记住一句话: traces.txt 告诉你「谁卡了」,logcat 告诉你「卡之前发生了什么」,bugreport 告诉你「整个系统的全景快照」。

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 瓶颈。

避坑指南: 我曾经因为 bugreport 太大(超过 50MB),在 Android Studio 里直接打开卡死。现在我会先用 zipinfo 看看大小,然后直接用命令行 sed 或 grep 提取关键段,不展开整个文件。

三个文件的对比总结表格

日志文件 核心价值 抓取时机 最大坑点
traces.txt 拍下 ANR 瞬间的所有线程堆栈 ANR 发生时自动生成 可能被多个 ANR 覆盖,老日志会被删除
logcat 时间线上追踪系统事件和 App 行为 可实时 / 可历史回溯 缓冲区可能被冲掉,需要提前设置大小
bugreport 全系统级快照,包含 CPU、内存、磁盘 手动触发,适合基准对比 文件极大,分析慢;部分版本信息不完整

分析流程总览

下面这张图总结了我自己平时排查 ANR 时“先看哪个、再看哪个”的套路。每次遇到 ANR,别急着翻代码,先把日志对齐。

ANR 日志分析核心流程 收到 ANR 反馈 抓取 traces.txt 看主线程堆栈状态 回查 logcat 搜 ANR / Input 超时 拉取 bugreport 看 CPU / 磁盘 / 服务 交叉比对时间戳与调用链 定位到具体代码行或锁对象 别忘了结合版本号对比回归 避免被旧日志误导

实际分析时,我一般先打开 traces.txt,找到主线程堆栈,判断是死锁、数据库慢查询还是反射耗时。如果是死锁,logcat 和 bugreport 里往往会有 held by thread X 的线索。如果是 I/O 问题,bugreport 里的 CPU 和磁盘情况能一锤定音。

你想想看,遇到 ANR 第一件事不是改代码,而是把这三个文件摆在一起,就像侦探把照片、监控录像和通话记录铺在桌上一样。嗯,这样才不容易漏线索。