第3章 系统ANR日志采集方法
3.1 现象识别
当主线程被阻塞超过5秒(前台应用)或10秒(后台应用),系统会弹出“应用无响应”对话框,同时生成ANR日志。现象表现为:
- 界面点击无反应,触摸事件丢失;
- 系统弹窗提示“XXX 应用无响应”,给出“等待”或“关闭”选项;
- 应用进程被系统标记为“ANR”,后续可能被_kill_并生成traces文件。
快速判断:如果用户反馈“点击按钮没反应”或“页面卡住几秒后闪退”,优先怀疑ANR。此时应立即收集日志,避免进程被系统回收后证据丢失。
3.2 数据采集
ANR日志是问题排查的核心证据。系统在ANR发生时自动保存trace文件,但需要主动采集才能获取。以下三种方法互为补充,按优先级推荐使用。
| 采集方法 | 命令/工具 | 产出文件 | 适用场景 |
|---|---|---|---|
| adb pull /data/anr | adb pull /data/anr/ ./anr_logs |
trace_*.txt(含主线程堆栈) | 设备root或工程版;快速获取最新trace |
| bugreport | adb bugreport bugreport.zip |
zip包内包含traces.txt、logcat、dump状态 | 生产环境无root;完整系统诊断 |
| logcat关键过滤器 | adb logcat -v time -b events | grep "am_anr" |
实时日志流 | 复现期监控;获取ANR触发时间戳 |
3.2.1 adb pull /data/anr
最直接的trace采集方式,需设备具备root权限或userdebug版本。ANR发生后,系统将主线程及所有线程的堆栈写入/data/anr/目录下。
# 检查是否有ANR文件
adb shell ls -l /data/anr/
# 复制全部trace文件到本地
adb pull /data/anr/ ./anr_logs
# 查看最新生成的trace(文件名含时间戳)
adb shell cat /data/anr/traces_2025-03-15_10-30-00.txt
关键字段解读:
main为应用主线程,Binder:xxx_1为Binder通信线程。若主线程处于BLOCKED或WAITING状态超过5秒,即为ANR嫌疑链。注意查看held by标记的锁持有者。
3.2.2 bugreport
推荐用于生产环境(非root设备)。bugreport采集整个系统状态,包含traces、logcat、CPU使用率、内存、网络等,信息最全面。
# 生成bugreport压缩包(Android 7+)
adb bugreport bugreport.zip
# 解压后关键文件:
# /FS/data/anr/traces.txt → 所有进程trace
# /FS/data/system/dropbox/ → ANR事件记录
# /FS/data/anr/ → 原始trace目录
注意:bugreport生成耗时约10~30秒,期间设备可能卡顿。建议在ANR复现后立即执行,但若进程已被系统杀死(trace文件丢失),可尝试从系统dump的
traces.txt中检索。
3.2.3 logcat关键过滤器
实时监控ANR事件,帮助定位触发精确时间点,以及前后CPU负载、内存压力等上下文信息。
# 过滤ANR事件(默认buffer)
adb logcat -v time -b main | grep -E "ANR in|am_anr"
# 使用events buffer记录AM(ActivityManager)内部ANR日志
adb logcat -v time -b events | grep "am_anr"
# 监控CPU高负载(可能导致主线程饥饿)
adb logcat -v time -b main | grep -E "Load:|Cpu|SystemServer"
logcat日志示例(events buffer):
03-15 10:30:00.123 1234 5678 I am_anr : [0,13742,com.example.app,141528,Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 2. Wait queue head age: 5200.0ms.)]
其中Input dispatching timed out指输入事件调度超时,age: 5200.0ms表明主线程已阻塞5.2秒。
3.3 原因定位
拿到trace文件后,定位主线程卡死原因的核心步骤:
- 找到主线程堆栈:在trace文件中搜索
"main" prio=5,查看其线程状态(RUNNABLE / BLOCKED / WAITING)。 - 检查锁竞争:若主线程为BLOCKED,看
waiting to lock <0x...>,对应的held by thread是谁。 - 分析CPU负载:若主线程为RUNNABLE但卡住,检查
cpu_load及系统线程(如SurfaceFlinger、renderthread)是否抢占了CPU。
// 示例trace片段(主线程BLOCKED,等待锁)
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7684d5a0 self=0x7a3b48e400
| sysTid=13742 nice=-10 cgrp=default sched=0/0 handle=0x7a4a0e0548
| state=Blocked stack=0x7fc3d63000-0x7fc3d65000
at com.example.app.MainActivity.onClick(MainActivity.java:125)
- waiting to lock <0x0987cdef> (a java.lang.Object) held by thread 15
at android.os.Handler.handleCallback(Handler.java:938)
...
"Thread-15" prio=5 tid=15 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7684d5a0 self=0x7a3b48e400
| state=Sleeping stack=0x7fc3d63000-0x7fc3d65000
at java.lang.Thread.sleep(Thread.java:-2)
at com.example.app.NetworkManager.getData(NetworkManager.java:80)
- locked <0x0987cdef> (a java.lang.Object)
快速定位技巧:用
grep -n "main" traces.txt | head -20定位主线程起始行,然后使用sed -n 'p;:a;n;/^$/q;p;ba'提取完整栈帧。
3.4 修复动作
根据定位结果采取针对性修复:
- 主线程耗时IO:将文件读写、网络请求等移至工作线程(AsyncTask / Executor / Coroutine)。
- 死锁:调整锁获取顺序,使用
tryLock或移除不必要的同步块。 - Binder超时:检查跨进程调用是否因服务端卡顿导致主线程等待,考虑异步回调或预加载。
- 布局测量过慢:优化XML层级,使用ConstraintLayout / RecyclerView,避免主线程做复杂计算。
// 修复前:主线程直接写文件
File file = new File(getFilesDir(), "data.json");
FileWriter fw = new FileWriter(file);
fw.write(json); // 可能阻塞主线程5秒以上
// 修复后:使用HandlerThread或WorkManager
Executors.newSingleThreadExecutor().execute(() -> {
File file = new File(getFilesDir(), "data.json");
try (FileWriter fw = new FileWriter(file)) {
fw.write(json);
} catch (IOException e) { /* handle */ }
});
3.5 复测与预防
修复完成后必须进行回归测试,确保ANR不再出现:
- 复测步骤:执行相同操作,同时后台运行logcat监控,观察ANR事件是否消失。
- 压力测试:使用Monkey或UI自动化工具模拟高强度交互,检查主线程反应时间。
- 预防机制:建立自动化ANR日志采集系统(如通过Market接口定期pull bugreport),并在开发阶段集成StrictMode检测主线程磁盘IO/网络调用。
最佳实践:在CI/CD流水线中增加“无ANR”断言——每次构建后运行5分钟压力测试,若logcat中出现
am_anr则标记构建失败。