2. ANR日志分析:读懂系统给你的遗言
ANR 一旦发生,系统会留下两份关键证据——traces.txt 和 logcat。我个人习惯先扫 logcat 快速定位问题范围,再翻 traces.txt 深挖堆栈细节。今天就把这两份日志怎么读、读什么讲透。
2.1 traces.txt 文件结构
文件位置在 /data/anr/traces.txt。你想想看,它是系统在 ANR 触发那一刻,对所有线程做的一次“快照”。结构非常清晰:
- 头部元数据:进程 ID、时间戳、系统 CPU 负载、进程 CPU 占比等。
- 线程列表:每个线程一段,以
----- pid xxxx at yyyy-MM-dd HH:mm:ss -----开头。 - 线程详细信息:包括线程状态、调用栈、持有的锁、等待的锁等。
核心字段速览
----- pid 12345 at 2025-03-20 14:30:00 -----
Cmd line: com.example.app
JNI: CheckJNI is off; workarounds are off; ...
CPU: 0.2% user + 0.1% kernel = 0.3% total
...
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x75123456
| self=0x7a123456
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7b123456
| state=S (sleeping) <-- 线程状态
| heldLock: <0x08abcdef> (a java.lang.Object) <-- 持有锁
| waiting on: <0x08deadbeef> (a java.lang.Object) <-- 等待锁
| waiting on held by thread: "binder:12345_2" tid=7 <-- 被谁持有
at java.lang.Object.wait(Native method)
at com.example.ui.MainActivity.onClick(MainActivity.java:120)
...
我曾遇到一个案例:主线程状态是 BLOCKED,等待一个锁,而那个锁被一个子线程持有了 30 秒没释放。顺着 waiting on held by thread 找到对应线程,一看它在做网络请求没有加超时。改完立马解决问题。
2.2 Logcat 中的 ANR 信息
Logcat 是第一条线索。ANR 发生后,系统会打印出几大类日志:
ANR in com.example.app– 宣布哪个进程 ANR。am_anr– 系统内部事件日志,包含 ANR 原因标识。CPU usage– 显示各进程 CPU 占用,判断是否被其他进程抢占。Total time/wait time– 超时时间与实际等待时间。
快速抓取命令:
adb logcat | grep -E "ANR|am_anr|traces"
为什么要先看 logcat?因为它告诉你 “为什么判定为 ANR”。比如:
2025-03-20 14:30:00.123 12345-12345 I am_anr : [0,12345,com.example.app,1,InputDispatch, ...]
这里面 InputDispatch 说明是输入事件超时,1 表示前台进程,优先级高。这些信息能帮你快速缩小排查方向。
2.3 关键字段速查表
| 字段 | 含义 | 常见值 / 例子 |
|---|---|---|
state |
线程当前状态 | RUNNABLE, BLOCKED, WAITING, NATIVE |
heldLock |
该线程正持有的锁 | <0x08abcdef> (a java.lang.Object) |
waiting on |
正在等待的锁 | <0x08deadbeef> |
waiting on held by thread |
锁被哪个线程持有着 | "binder:12345_2" tid=7 |
CPU usage |
进程 CPU 使用率(logcat 中) | 50% user + 10% kernel = 60% total |
am_anr 原因 |
ANR 分类标识 | InputDispatch, BroadcastTimeout, ServiceTimeout |
2.4 避坑指南:我踩过的几个雷
不要只看主线程堆栈
我曾经接手一个 ANR,主线程明明在 epollWait 休眠,看起来没问题。后来发现是另一个线程死循环占满了 CPU,导致主线程得不到调度。traces.txt 头部上有 CPU usage,一看该进程 CPU 占 95%,立马定位到了那个疯狂循环的线程。所以记得先扫一眼头部负载信息。
锁等待链要追踪到底
看到 waiting on held by thread 后,跳到那个线程的堆栈。如果它也在等另一个锁,继续追。直到找到真正卡住不动的线程。我习惯把这个链画出来——用纸笔或者脑图都行。
2.5 知识体系:ANR 日志分析全流程
下面这张图帮你理清思路:
2.6 实战小技巧:一条命令搞定三份信息
每次 ANR 我都是这么操作的:
# 拉取 traces.txt
adb pull /data/anr/traces.txt
# 同时过滤 logcat 中的 ANR 相关日志
adb logcat -d | grep -E "ANR|am_anr|CPU usage" > anr_log.txt
# 用编辑器(如 VScode)同时打开两个文件,对照着看
这样做的好处是:logcat 告诉你“发生了什么”,traces.txt 告诉你“当时线程在做什么”。两相对照,95% 的 ANR 都能在一刻钟内锁定根因。剩下的 5% 嘛……嗯,可能就要配合 systrace 了,那是后话。