3. 获取ANR日志的标准化方法

3.1 现象识别:何时需要采集ANR日志

ANR 发生时,系统会在 /data/anr/ 目录下生成 traces.txt 文件,记录所有线程的堆栈快照。并非每次 ANR 都需要采集完整日志,应依据以下判断规则决定采集深度:

场景 现象特征 采集策略
偶现 ANR 无固定复现路径,用户操作差异大 必采 traces.txt + logcat
必现 ANR 固定操作步骤即可触发 优先 Profiler 实时捕获
线上大面积 ANR 版本更新后爆发,影响面广 立即触发 BugReport + 线上平台
低端设备 ANR 仅在低内存/低性能设备出现 留痕采集 + 内存快照
采集红线:生产环境禁止直接执行 adb 命令。线上 ANR 必须依赖预埋的日志采集框架自动上报,不得通过远程连接设备拉取 traces.txt,否则会破坏现场且违反用户隐私协议。

3.2 数据采集:四种标准化方法

3.2.1 adb 命令拉取 traces.txt

适用于线下调试机或内测设备,是获取 ANR 现场最直接的手段。执行前先确认 traces 文件是否存在:

# 检查 ANR traces 文件是否存在
adb shell ls -l /data/anr/

# 拉取最新 traces.txt 到本地
adb pull /data/anr/traces.txt ./anr_logs/

# 如果 traces.txt 过大,只查看主线程堆栈
adb shell cat /data/anr/traces.txt | grep -A 30 "main"
最佳实践:拉取后立即执行 adb logcat -d > logcat.txt 保存 logcat,与 traces.txt 配对分析。traces.txt 仅记录堆栈快照,缺少时序日志时很难还原前置操作。

3.2.2 BugReport 全量日志

BugReport 包含 traces.txt、logcat、系统服务状态、内存信息和 CPU 负载,适用于需要综合分析系统环境的场景。采集命令:

# 生成 BugReport(耗时 1-3 分钟)
adb bugreport ./bugreport_output/

# 解压后关键文件路径
# bugreport-设备名-日期.zip
#   ├── bugreport-设备名-日期.txt
#   ├── FS/data/anr/traces.txt
#   └── dumpstate_xxx.txt

BugReport 适合与竞品对比或系统级 ANR 排查,但文件体积常在 50 MB 以上,不适合批量上传。

3.2.3 Android Studio Profiler 实时捕获

在开发阶段,使用 Android Studio Profiler 的 CPU Profiler 或 Memory Profiler 实时观察线程状态,可以在 ANR 发生前捕获到主线程长时间阻塞的征兆。

  • 适用时机:必现 ANR,且需要在 IDE 中实时观察 CPU 与线程调度。
  • 操作步骤:连接设备 → 打开 Profiler → 复现 ANR → 冻结时间轴 → 导出 Call Chart。
  • 优势:可视化展示方法调用链,定位到具体类与方法行号。
  • 局限:Profiler 本身会带来 5%-15% 的性能开销,低端设备上可能改变 ANR 复现条件。

3.2.4 线上日志采集平台

线上 ANR 必须依赖自动化采集平台。主流方案对比如下:

平台 接入成本 日志丰富度 实时性 典型场景
Firebase Crashlytics 低(Gradle 插件) 堆栈 + 自定义键值对 分钟级 中小团队快速上线
自建平台(ELK/日志网关) 高(需搭建后端) 全量日志 + 堆栈 + 埋点 准实时 大规模用户、定制化需求
友盟 / Bugly 低 堆栈 + 设备信息 天级报表 国内 Android 生态
线上日志自建方案关键指标:
• 每条 ANR 日志大小控制在 64 KB 以内(含堆栈 + 最近 200 条 logcat)
• 上报频率不超过 1 次 / 分钟 / 设备,避免流量与存储爆炸
• 必须包含 elapsedRealtime 时间戳,用于对齐系统服务状态

3.3 原因定位:从日志中提取关键线索

拿到 traces.txt 后,按 三看原则 快速定位:

  1. 看主线程状态:搜索 "main" 或 "tid=1",检查是否处于 BLOCKED、WAITING、SLEEPING 状态。
  2. 看锁竞争链:找到 waiting to lock 与 held by 配对行,识别死锁或长时间持锁。
  3. 看 Binder 调用:搜索 Binder:793_1 等线程,判断是否因系统服务响应超时导致 ANR。
// traces.txt 关键字段示例(ANR 主线程阻塞在锁等待)
"main" tid=1 BLOCKED
  at com.example.app.MainActivity.onClick(MainActivity.java:85)
  - waiting to lock <0x0a4b8c00> (a java.lang.Object)
  at com.example.app.DataManager.loadData(DataManager.java:120)
  - locked <0x0a4b8c00> (a java.lang.Object)  <-- 持锁线程在此

3.4 修复动作:基于日志的优化方向

日志线索 常见根因 修复动作
主线程 BLOCKED + waiting to lock 子线程持锁未释放 使用 synchronized 缩小锁范围,或改用 ReadWriteLock
主线程执行 SQL / 网络 I/O 在主线程做磁盘或网络操作 迁移到 AsyncTask / Coroutine(Dispatchers.IO)
Binder 调用长时间未返回 系统服务(AMS/WMS)负载过高 减少跨进程调用频率,增加本地缓存
主线程 CPU 密集计算 图片解码/JSON 解析在主线程 使用 WorkManager 或 RxJava 调度到后台线程

3.5 复测:验证修复有效性

修复后必须按 三级复测 流程确认 ANR 已消除:

  • L1 单元复测:在修复分支上,用 Profiler 人工复现原操作路径,确认主线程不再出现 BLOCKED 状态。
  • L2 压力复测:使用 adb shell am instrument 或 Monkey 脚本连续执行目标操作 1000 次,观察是否触发 ANR。
  • L3 线上灰度验证:发布至 5% 灰度用户,观察 Crashlytics/自建平台中 ANR 率是否下降至基线水平。
# 压力复测示例:对特定 Activity 执行 500 次启动
adb shell am instrument -w -e class com.example.app.MainActivityTest \
  com.example.app.test/androidx.test.runner.AndroidJUnitRunner

3.6 预防:建立日志采集规范

从流程上避免 ANR 日志采集遗漏,需固化以下 四条规则:

  1. 发布前绑定采集配置:每次构建打包时,通过 Gradle 任务自动注入线上日志采集开关与采样率。
  2. 统一日志格式:所有 ANR 上报字段必须包含 thread_name、thread_state、lock_info、cpu_usage 四个核心字段。
  3. 本地留痕兜底:在 Context.getCacheDir() 下保留最近 3 次 ANR 的 traces 副本,防止网络上报失败后丢失现场。
  4. 定期巡检日志覆盖率:每周检查线上 ANR 日志采集率(上报 ANR 数 / 实际 ANR 数),低于 90% 需排查采集管道。
采集率计算公式: 采集率 = (上报到平台的 ANR 数量) / (Android 系统记录的 ANR 数量) × 100%。系统记录可通过 adb shell dumpsys activity processes | grep "anr" 查看。
ANR 日志采集方法决策流程 ANR 发生 环境判断 <