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 发生时,不要直接看堆栈,先按以下故障树定位分类:

故障树判断规则:
  1. 收集 traces.txt 与 event log 第一行。
  2. 在 event log 中搜索 am_anr 标签,提取 reason 字段。
  3. reason 包含 input -> 归为输入 ANR;包含 broadcast -> 归为广播 ANR。
  4. reason 为 keyDispatchingTimedOut,且前序消息堆栈包含 bindApplication -> 启动 ANR。
  5. reason 为 executingService -> 服务 ANR。
  6. 无法直接判定时,查看主线程堆栈顶部 Method 是否属于 Framework 生命周期的回调方法。
ANR 分类决策树 event log 提取 am_anr reason 含 "input" keyDispatchingTimedOut broadcast/service 输入 ANR ✓ 前序含 bindApplication? reason 字段细分 启动 ANR ✓ 输入 ANR ✓ 广播/服务 ANR 提示:所有 ANR 判定必须结合 traces.txt 主线程堆栈 + event log 的 am_anr 行,单靠堆栈可能误判。

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 数据采集:明确取证范围

在进入原因定位前,先完成以下数据采集清单,确保分类依据充足:

  1. 必选: traces.txt 文件(ANR 发生时自动生成,位于 /data/anr/traces.txt)。
  2. 必选: adb logcat -b events -d | grep am_anr 提取事件日志。
  3. 必选: adb logcat -d | grep -E "ANR|am_anr|watchdog" 获取系统日志摘要。
  4. 可选: 如果问题可复现,开启 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 或线程池,回调仅做调度