第3章 ANR日志分析方法(traces.txt, logcat)
咱们直接切入正题。排查ANR,说白了就是跟两份日志打交道:traces.txt 和 logcat。一个像尸检报告,记录死前最后一刻的现场;另一个像监控录像,记录事发前后的系统行为。两样缺一不可。
我个人习惯先把logcat拉出来扫一眼,拿到进程名和时间点,再去蹲traces.txt。顺序别搞反了,否则你会一头扎进堆栈的海洋里出不来。
traces.txt —— ANR现场快照
位置固定在 /data/anr/traces.txt。每次ANR发生后,系统会把所有线程的调用栈 dump 到里面。注意——它只保留最近几次的ANR记录,不是从开机到现在全存着。
/data/anr/traces/ 下面按日期分文件。所以,拿到手机型号后先确认一下路径。
拉取方法很简单:
adb root
adb pull /data/anr/traces.txt .
adb shell cat /data/anr/traces.txt | grep "main" -A 30 # 快速看主线程
打开文件后,你会看到类似这样的结构:
----- pid 12345 at 2024-08-20 10:15:30 -----
Cmd line: com.example.myapp
"main" prio=5 tid=1 Native
at android.os.BinderProxy.transactNative(Native Method)
at android.content.ContentProviderProxy.query(ContentProviderProxy.java:...)
at ...
at com.example.myapp.MainActivity.onCreate(MainActivity.java:42)
...
"Thread-2" prio=5 tid=2 Blocked
at java.lang.Object.wait(Native Method)
- waiting on <0x0a1b2c3d> (a java.lang.Object)
...
核心字段解读:
| 线程名 | 如 "main" 就是UI线程,其他是子线程 |
| prio | 优先级,一般5是默认 |
| 状态 | Native / Runnable / Blocked / Waiting / TimedWaiting |
| 堆栈 | 从最底层到最顶层,看哪个方法卡住 |
| 锁信息 | waiting on / locked 标记了等待什么锁 |
logcat —— ANR发生前的「监控录像」
logcat里通常会打出下面这样的ANR关键行:
I/WindowManager( 1234): ANR in com.example.myapp (com.example.myapp/.MainActivity)
I/WindowManager( 1234): Reason: Input dispatching timed out (Waiting to send non-key event ...)
I/WindowManager( 1234): CPU usage from 1234ms to 0ms ago (2024-08-20 10:15:28.123 to 10:15:29.347):
0.0% 1234/system_server: 0.0% user + 0.0% kernel / faults: 0 minor 0 major
99% 5678/com.example.myapp: 98% user + 1% kernel / faults: 100 minor 12 major
...
I/WindowManager( 1234): Total: 99% user + 1% kernel
这几行信息量很大:
- Reason:ANR的原因类型。最常见的是
Input dispatching timed out(5秒没响应)、Broadcast of Intent(广播10秒)、Service Timeout(服务20秒)。 - CPU usage:ANR发生前几秒内各进程的CPU占用情况。如果你的app CPU占了99%,排除一下是不是死循环。如果CPU不到1%,说明线程在sleep/wait或者IO堵塞。
- faults:major faults 表示大量缺页中断(内存不够,频繁swap),会导致长时间阻塞。我曾经遇到过一个ANR,CPU占用只有5%,但major faults > 1000,一看是Bitmap没回收导致内存压力大。
另外,如果ANR涉及ContentProvider或Binder调用,logcat里还会打出:
W/ActivityManager( 1234): ANR: ContentProvider not responding after 10s
I/ActivityManager( 1234): Waiting for provider: com.example.myapp.provider.MyProvider
两种日志搭配使用 —— 实战演练
说个我亲手排查的案例。某用户反馈App点击后卡死,持续约10秒恢复。我的分析步骤:
- 抓logcat 发现 Reason 是 "Input dispatching timed out",CPU usage显示app进程占用0%,但 major faults 很高。
- 看traces.txt 主线程停在
SQLiteAccess的wait()上,等待一个写锁。 - 查源码 发现 Room 的一个事务在子线程执行了全表更新,耗时15秒。主线程因为需要读取同一张表,被事务的写锁阻塞。
- 解决:把Room的查询也放到协程里,或者加一个超时机制。
这儿有个对比表格,帮你快速选看哪个日志:
| 场景 | 优先看traces.txt | 优先看logcat |
|---|---|---|
| 不知道哪个进程ANR | ❌ 不直观 | ✅ 直接打出package name |
| 需要线程堆栈细节 | ✅ 完整调用链 | ❌ 只显示简单堆栈(有时有partial stack) |
| 分析CPU/IO/内存压力 | ❌ 无 | ✅ CPU usage + faults |
| 查看锁竞争关系 | ✅ waiting on / locked 明确 | ❌ 一般没有锁信息 |
实战小贴士:如何一分钟定位「谁在搞鬼」
拿到traces.txt后,我通常会这么快速扫描:
# 只看主线程堆栈,找关键词
grep -A 30 '"main"' traces.txt
# 看有哪些线程处于 BLOCKED 或 WAITING
grep -E "(Blocked|Waiting)" traces.txt
# 查看所有锁对象等待链
grep -E "waiting on|locked" traces.txt
嗯,这里要注意:有些ANR是系统自己引发的,比如系统服务超时。这时候traces.txt里你的app堆栈可能是正常的,但logcat里会显示系统进程的ANR。别钻牛角尖。
还有一个坑:Android 10及以上,traces.txt 对常见App路径做了混淆/限制,你去pull可能会报 Permission denied。可以用 adb bugreport 把整个报告抓下来,里面有个 FS/data/anr/ 目录包含traces.txt。
- 先看logcat的CPU usage → 判断是死循环(高CPU) 还是 IO/锁等待(低CPU)
- 再看traces.txt主线程堆栈 → 精准定位卡住的方法
- 结合锁信息和等待链 → 还原死锁或等待场景
- 最后回查代码 → 修复
最后扔个终极技巧:如果你遇到的是「伪ANR」——用户觉得卡,但系统没报ANR日志。这时候 traceview 或 systrace 比traces.txt更管用。当然,那是另一个话题了。