2. ANR 日志采集与初步分析:traces.txt、logcat、dumpsys 输出解读
ANR 排查的第一步不是看代码,而是拿到正确的现场日志。Android 系统在 ANR 发生时及发生后,会在设备上留下三类核心诊断数据:traces.txt、logcat 缓冲区、dumpsys 服务快照。本章从问题排查闭环出发,依次讲解每种日志的现象识别→数据采集→关键信息提取→初步修复方向→复测方法→预防措施,并给出可直接用于日常工作的排查规则与决策树。
2.1 现象识别 — ANR 发生的典型表现
ANR 触发时,用户直接感知到的是界面无响应。系统会弹出 ANR 对话框(“应用无响应”),并在后台输出标记日志。从技术角度,识别 ANR 有四个可靠信号:
| 信号来源 | 具体表现 | 判断规则 |
|---|---|---|
| 用户界面 | 触摸、按键无反馈;屏幕出现 ANR 弹窗 | 弹窗标题包含 "无响应" 或 "not responding" |
| logcat (events) | 日志行中出现 am_anr 标签 | |
| traces.txt | /data/anr/ 目录下生成新的 trace 文件 | 文件时间戳与 ANR 时刻匹配 |
| 系统广播 | ACTION_ANR 系统广播(部分定制 ROM) | BroadcastReceiver 可捕获 |
✅ 快速判断规则:当用户反馈“卡死”时,优先执行
adb logcat -b events -d | grep am_anr,若有输出则 ANR 确认;然后检查 /data/anr/traces.txt 的时间戳,锁定现场。
2.2 数据采集 — 三大日志的获取方法
ANR 现场转瞬即逝,数据采集必须在问题复现后 30 秒内完成,否则 traces.txt 可能被覆盖或 GC 回收。以下为标准采集步骤:
2.2.1 traces.txt — 主线程堆栈与锁快照
- 位置:
/data/anr/traces.txt(每次 ANR 会附加到该文件末尾) - 采集命令:
adb shell ls -l /data/anr/traces.txt # 检查文件存在 adb pull /data/anr/traces.txt ./anr_traces # 拉取到本地 - 注意事项:某些系统级 ANR 需要 root 权限;低版本(Android 6 以下)可能存储在
/data/anr/下多个 trace 文件。
2.2.2 logcat — 事件日志与 CPU 负载
- 事件日志(events buffer):包含 ANR 触发原因、进程名、CPU 使用率。
- 采集命令:
adb logcat -b events -d | grep am_anr > anr_events.log adb logcat -b main -d -v threadtime > anr_main.log - 关键字段:
am_anr: [0,1234,com.example.app,Input dispatching timed out],其中reason字段说明 ANR 类型(输入超时 / 广播超时 / 服务超时)。
2.2.3 dumpsys — 服务与内存快照
- 作用:获取 ANR 发生前后系统的内存压力、 binder 线程池状态、前台服务等信息。
- 采集命令:
adb shell dumpsys meminfo com.example.app > meminfo.txt adb shell dumpsys cpuinfo > cpuinfo.txt adb shell dumpsys activity processes > process_status.txt - 核心指标:PSS(按比例分配的物理内存)、线程数、binder 线程活跃数。
⚠️ 采集避坑指南:(1)不要先做 adb root 再 pull,部分设备会因权限变化覆盖 trace 文件;(2)logcat 缓冲区有容量上限,ANR 发生后优先拉取 events buffer;(3)dumpsys 命令可能耗时较长,建议在 ANR 后 15 秒内并发执行。
2.3 原因定位 — 从日志中锁定根因
获取到三类日志后,按照“主线程堆栈 → 锁依赖图 → CPU/内存负载”的联合解读流程定位根因。以下为最常见的三种模式及其判断规则:
| ANR 模式 | traces.txt 特征 | logcat 特征 | dumpsys 特征 |
|---|---|---|---|
| 输入超时 | 主线程在 InputMethodManager 或 View.dispatchTouchEvent 处卡住 | reason 包含 "Input dispatching timed out" | CPU 负载通常较低,binder 线程空闲 |
| 广播超时 | 主线程在 BroadcastReceiver.onReceive 中执行了网络/DB 操作 | reason 包含 "Broadcast of Intent" + 超时时间 | CPU 可能较高,PSS 正常 |
| 死锁 | 主线程处于 BLOCKED 状态,等待锁 "A",而持有锁 "B" 的线程也在等待主线程 | 通常无额外 CPU 异常 | 线程数异常增加,binder 线程池耗尽 |
2.3.1 主线程堆栈分析规则
在 traces.txt 中搜索主线程标识("main" tid=1 或 "main" prio=5),关注以下状态码:
- RUNNABLE:主线程正在执行代码,通常是耗时计算或 I/O — 需排查
onClick、onCreate、onBind等回调中的同步操作。 - BLOCKED:主线程在等待锁 — 定位
waiting to lock后面的对象,寻找持有线程。 - WAITING / TIMED_WAITING:主线程在
Object.wait()或Thread.sleep()— 多为跨线程协作设计不当。
🔍 排查故障树(症状 → 根因速查):
症状 A:主线程 RUNNABLE + 堆栈内有 File/DB/Network 调用 → 根因为主线程做同步 I/O
症状 B:主线程 BLOCKED + 堆栈含 synchronized/Object.wait → 根因为死锁或锁竞争
症状 C:主线程 RUNNABLE + 堆栈在 View 绘制/布局 → 根因为过度绘制或层级过深
症状 A:主线程 RUNNABLE + 堆栈内有 File/DB/Network 调用 → 根因为主线程做同步 I/O
症状 B:主线程 BLOCKED + 堆栈含 synchronized/Object.wait → 根因为死锁或锁竞争
症状 C:主线程 RUNNABLE + 堆栈在 View 绘制/布局 → 根因为过度绘制或层级过深
2.3.2 锁信息与线程依赖分析
在 traces.txt 中,每个线程栈末尾会列出当前持有的锁(held lock)和等待的锁(waiting to lock)。通过手工或脚本构建锁依赖图:
# 伪代码:从 traces.txt 提取锁依赖
grep -E "(held lock|waiting to lock)" traces.txt | sort | uniq -c
当检测到循环等待(线程 A 等 B 的锁,B 等 A 的锁),即为死锁,必须通过锁排序或锁超时机制修复。
2.3.3 CPU 与内存负载联合判断
从 logcat 的 am_anr 行中可提取 ANR 时刻的 CPU 使用率:
am_anr: [0,1234,com.example.app,Input dispatching timed out]
CPU usage: 12% total, 8% user, 4% kernel, 2% I/O wait
- 总 CPU > 30%:系统繁忙,可能是后台任务或系统服务异常,需结合 dumpsys cpuinfo 确认高负载进程。
- 总 CPU < 10% + 主线程 BLOCKED:大概率死锁或锁竞争,与系统负载无关。
- PSS 异常高(超过正常值 2 倍):内存压力可能导致 GC 频繁,进而引发主线程 GC 停顿,需检查
dumpsys meminfo中的Objects和GC指标。
2.4 修复动作 — 基于日志的直接修复方向
根据定位结果,采取对应的修复策略(本章仅给出方向性动作,详细代码实现在后续章节展开):
| 定位结果 | 直接修复动作 | 预期改善 |
|---|---|---|
| 主线程同步 I/O | 将文件/网络/DB 操作移至 AsyncTask、Coroutine(Dispatchers.IO) 或 WorkManager | 主线程不再阻塞,ANR 消除 |
| 死锁 | 统一锁获取顺序;使用 tryLock 带超时;或用 ReentrantLock 替代 synchronized | 锁循环等待被打破 |
| 过度绘制 / 布局过深 | 简化布局层级;使用 ConstraintLayout;开启 GPU 过度绘制调试 | 减少主线程每帧工作量 |
| 内存压力导致 GC 停顿 | 优化对象创建(对象池 / 避免频繁分配);检查 Bitmap 复用 | GC 频率降低,主线程停顿减少 |
2.5 复测 — 验证修复是否有效
修复后必须在相同环境(设备型号 / 系统版本 / 操作路径)下复测,使用与采集阶段相同的命令验证:
- 步骤 1:执行相同的用户操作序列,观察是否再次弹出 ANR 对话框。
- 步骤 2:在操作过程中持续运行
adb logcat -b events | grep am_anr,确认无新 ANR 事件。 - 步骤 3:检查
/data/anr/traces.txt文件大小是否增长(若仍增长,说明还有未修复的 ANR)。 - 步骤 4:使用
adb shell dumpsys cpuinfo和meminfo对比修复前后的 CPU 和内存指标,确保性能回归。
📊 复测通过标准:连续 10 次操作复现路径均无 ANR,且 traces.txt 文件在测试期间无新增主线程 BLOCKED 或 WAITING 记录。
2.6 预防 — 建立日志监控与性能基线
防止 ANR 再次出现,需要在开发流程中嵌入自动化预防措施:
- 自动化日志采集:在测试脚本中集成
adb logcat -b events -v time | grep am_anr,一旦出现 ANR 自动截图并保存 traces.txt。 - 性能基线看板:将
dumpsys cpuinfo和meminfo的关键指标(主线程 CPU%、PSS、线程数)纳入 CI 看板,每次提交对比基线。 - 代码审查检查点:在 MR 审查中强制检查是否在主线程调用了网络/DB/文件操作,使用
StrictMode在开发阶段就发现主线程 I/O。 - 线上监控:接入自定义 ANR 监控(如 BlockCanary 或 Matrix),采集主线程堆栈上传到后端,形成 ANR 热力图。
📌 预防核心原则:主线程只做 UI 布局和轻量计算,所有可能超过 5ms 的操作都必须异步化。在提交前执行
adb shell dumpsys activity processes | grep "oom" 检查进程状态,确保没有异常线程。