第3章 系统ANR日志采集方法

3.1 现象识别

当主线程被阻塞超过5秒(前台应用)或10秒(后台应用),系统会弹出“应用无响应”对话框,同时生成ANR日志。现象表现为:

  • 界面点击无反应,触摸事件丢失;
  • 系统弹窗提示“XXX 应用无响应”,给出“等待”或“关闭”选项;
  • 应用进程被系统标记为“ANR”,后续可能被_kill_并生成traces文件。
快速判断:如果用户反馈“点击按钮没反应”或“页面卡住几秒后闪退”,优先怀疑ANR。此时应立即收集日志,避免进程被系统回收后证据丢失。
ANR排查流程:从现象到预防 ① 现象识别 界面无响应弹窗 ② 数据采集 adb pull / bugreport / logcat ③ 原因定位 Trace / CPU / 堆栈分析 ④ 修复动作 代码修改 / 异步化 / 优化 ⑤ 复测与预防 自动化采集 / 边界优化 ⚠️ 发现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文件后,定位主线程卡死原因的核心步骤:

  1. 找到主线程堆栈:在trace文件中搜索"main" prio=5,查看其线程状态(RUNNABLE / BLOCKED / WAITING)。
  2. 检查锁竞争:若主线程为BLOCKED,看waiting to lock <0x...>,对应的held by thread是谁。
  3. 分析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则标记构建失败。