ANR 现象识别与分类
ANR(Application Not Responding)是 Android 系统对主线程超时阻塞的最终裁决。无论应用架构多优秀,ANR 一旦发生,用户体验直接归零。本章聚焦于现象识别与分类边界,在动手优化前,先建立一套可执行的判断规则,确保每一个 ANR 都能被正确归类。
1.1 四类 ANR 的核心特征
系统根据触发场景将 ANR 分为四个主要类型:启动 ANR、输入 ANR、广播 ANR 和服务 ANR。不同类型对应不同的超时阈值与堆栈特征,以下是判定清单:
| 类型 | 触发场景 | 默认超时 | 主线程阻塞特征 |
|---|---|---|---|
| 启动 ANR | Activity.onCreate / onResume | 5s | onCreate 中执行网络请求或 IO 操作 |
| 输入 ANR | 按键 / 触摸事件分发 | 5s | 主线程 Looper 被前序消息或同步屏障阻塞 |
| 广播 ANR | BroadcastReceiver.onReceive | 10s(前台)/ 60s(后台) | onReceive 执行耗时操作,未使用 goAsync |
| 服务 ANR | Service.onCreate / onStartCommand / onBind | 20s | 服务生命周期回调中执行同步任务 |
1.2 故障树:快速判定 ANR 类型
当 ANR 发生时,不要直接看堆栈,先按以下故障树定位分类:
故障树判断规则:
- 收集
traces.txt与event log第一行。 - 在 event log 中搜索
am_anr标签,提取reason字段。 - reason 包含
input-> 归为输入 ANR;包含broadcast-> 归为广播 ANR。 - reason 为
keyDispatchingTimedOut,且前序消息堆栈包含bindApplication-> 启动 ANR。 - reason 为
executingService-> 服务 ANR。 - 无法直接判定时,查看主线程堆栈顶部
Method是否属于 Framework 生命周期的回调方法。
1.3 排查表:从日志到分类
以下排查表列出每种 ANR 在 event log 中的典型模式,以及对应的堆栈位置:
| ANR 类型 | 事件日志 (event log) |
主线程堆栈位置 | 附加特征 |
|---|---|---|---|
| 启动 ANR | am_anr [0,60000,...,keyDispatchingTimedOut] |
android.app.ActivityThread.handleResumeActivity |
堆栈可见网络/IO 回调, Activity 生命周期未完成 |
| 输入 ANR | am_anr [0,5000,...,input] |
android.view.InputEventReceiver.dispatchInputEvent |
前序消息可能是 long msg 或 barrier |
| 广播 ANR | am_anr [0,10000,...,broadcast of Intent {...}] |
BroadcastReceiver$PendingResult.finish |
未调用 goAsync 或 onReceive 耗时 >10s |
| 服务 ANR | am_anr [0,20000,...,executingService] |
android.app.ActivityThread.handleServiceArgs |
Service 生命周期回调中 同步执行了耗时任务 |
实战贴士: 使用以下命令一键提取最近一次的 ANR event log 行,结合 grep 过滤类型:
adb logcat -b events -d | grep "am_anr" | tail -1
# 典型输出示例:
# I/am_anr( 985): [0,60000,com.example.app,7329,keyDispatchingTimedOut]
其中第三位 60000 是等待时间,第四位是包名,第五位是 PID,第六位是 ANR 类型关键字。
1.4 判断规则:防止误分类
以下规则用来处理边界情况,避免将系统 ANR 误判为应用 ANR,或在不同类型间来回切换:
- 规则 1: 如果堆栈顶部是
nativePollOnce且 event log 没有am_anr,
这不是 ANR,是系统采样延迟,直接跳过。 - 规则 2: 广播 ANR 如果 reason 包含
forced后缀,说明系统主动杀进程,
需要配合lowmemorykiller日志分析,不单独做主线程优化。 - 规则 3: 输入 ANR 中如果检测到
InputDispatcher::notifyANR但主线程堆栈为binder transaction,
优先定位前台服务的 Binder 调用超时,而非主线程自身。 - 规则 4: 同一进程连续触发多类 ANR 时,以首次 ANR 类型为准,
后续 ANR 可能是连锁反应,修复主因即可。
1.5 数据采集:明确取证范围
在进入原因定位前,先完成以下数据采集清单,确保分类依据充足:
- 必选:
traces.txt文件(ANR 发生时自动生成,位于/data/anr/traces.txt)。 - 必选:
adb logcat -b events -d | grep am_anr提取事件日志。 - 必选:
adb logcat -d | grep -E "ANR|am_anr|watchdog"获取系统日志摘要。 - 可选: 如果问题可复现,开启
strict mode抓取主线程违规记录:StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() // 严重阻塞可设为 ANR 前触发 .build());
常见陷阱: traces.txt 文件在多进程应用下可能包含多个进程的堆栈。务必通过
cmd= 或 pid= 行区分是主进程还是子进程。子进程的 ANR 不直接引发 UI 阻塞,但会导致功能模块无响应,分类时归为服务 ANR 的子类 后台 ANR。
1.6 修复方向与预判
一旦完成分类,直接进入修复动作预判:
| ANR 类型 | 常见根因 | 预修复方向 |
|---|---|---|
| 启动 ANR | onCreate 做网络/DB/IO 操作 | 异步初始化、懒加载、Splash 阶段 |
| 输入 ANR | 主线程消息队列被同步屏障或长消息卡住 | 优化长任务、拆分消息或使用 Choreographer |
| 广播 ANR | onReceive 直接执行耗时同步任务 | 使用 goAsync 或 WorkManager 转发 |
| 服务 ANR | 服务回调中直接执行网络/DB 操作 | 启用 JobScheduler 或线程池,回调仅做调度 |