4. ANR触发机制与系统源码解析

ANR(Application Not Responding)并不是一个单一事件,而是系统在多个关键路径上对应用响应能力进行超时检测后,统一汇总到 ActivityManagerService 并弹出对话框的结果。理解 ANR 的关键,不是记住“主线程卡了 5 秒”,而是搞清楚四类 ANR 分别由谁计时、计时多久、在什么条件下判定、以及最终如何生成 traces 与日志。本节从系统源码视角拆解 Input、Broadcast、Service、ContentProvider 四类 ANR 的触发链路。

一、核心原理:四类 ANR 的计时主体与判定入口

Android 中 ANR 的判定入口集中在 ActivityManagerService(AMS)及其辅助类。不同组件由不同服务负责“喂狗”或“超时回调”,但最终都会调用 appNotResponding() 系列方法。四类 ANR 的计时主体如下:

  • Input ANR:由 InputDispatcher 检测输入事件分发超时,通过 WindowManagerService 回调到 AMS。
  • Broadcast ANR:由 AMS 的 BroadcastQueue 在派发广播时设置超时消息,前台/后台队列阈值不同。
  • Service ANR:由 AMS 的 ActiveServices 在 serviceDoneExecuting 未按时到达时触发。
  • ContentProvider ANR:由 AMS 在 publishContentProviders 阶段等待应用发布 provider 超时触发。

四类 ANR 的共同点是:系统在某个“必须由应用主线程完成”的环节设置了超时阈值,一旦超时且应用进程未处于可豁免状态(如调试中、已崩溃、正在关机等),就会判定 ANR。

四类 ANR 触发链路与超时阈值 Input 事件 InputDispatcher 分发 5s / 无响应回调 Broadcast 广播 BroadcastQueue 派发 前台 10s / 后台 60s Service 服务 ActiveServices 调度 前台 20s / 后台 200s ContentProvider publishContentProviders 10s 发布超时 ActivityManagerService appNotResponding() 统一判定入口 判定前置条件检查 进程是否已崩溃 / 是否处于调试豁免 / 是否正在关机 主线程是否仍在阻塞 / 是否已有 ANR 正在处理 生成 ANR 报告 traces.txt + main log + event log + dropbox 弹出 “应用无响应” 对话框

二、四类 ANR 的触发条件与超时阈值

不同 Android 版本阈值略有差异,但整体稳定。下表以 AOSP 主线版本为参考,列出四类 ANR 的触发条件、默认阈值与关键源码位置。

ANR 类型 触发条件 默认超时阈值 关键源码/类
Input ANR 输入事件分发后,应用在超时时间内未完成处理,且无新事件消费 5s(部分场景 8s) InputDispatcher.cpp、InputManagerService
Broadcast ANR 前台/后台广播接收器 onReceive() 未在阈值内执行完毕 前台 10s,后台 60s BroadcastQueue.java、BroadcastReceiver
Service ANR Service 启动、绑定或前台化后,未按时回调 serviceDoneExecuting 前台 20s,后台 200s ActiveServices.java、ServiceRecord
ContentProvider ANR 应用进程启动后未在阈值内发布 ContentProvider 10s ActivityManagerService.java、ContentProviderRecord
提示:前台与后台的区分依据是进程优先级(oom_adj / procState)。前台进程、可见进程通常按前台阈值判定;后台进程、空进程按后台阈值判定。因此同一个广播接收器,在不同进程状态下可能触发不同超时。

三、系统判定流程:从超时到 ANR 报告

以 Broadcast ANR 为例,源码链路大致如下:

// BroadcastQueue.java 简化逻辑
final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) {
    r.receiver = app.thread;
    r.curApp = app;
    app.curReceiver = r;
    // 设置超时消息
    mHandler.sendMessageDelayed(
        mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, r),
        r.receiver.getType() == FOREGROUND ? BROADCAST_FG_TIMEOUT : BROADCAST_BG_TIMEOUT);
    // 调用应用 onReceive
    app.thread.scheduleReceiver(...);
}

// 应用执行完毕后回调
public void finishReceiver(IBinder who, int resultCode, ...) {
    // 移除超时消息
    mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, r);
    ...
}

如果 finishReceiver 未在阈值内到达,BROADCAST_TIMEOUT_MSG 被触发,最终调用 ActivityManagerService.appNotResponding()。该方法会执行以下关键动作:

  1. 检查进程是否已处于 ANR 状态,避免重复报告。
  2. 检查是否处于调试豁免、崩溃恢复、关机等特殊状态。
  3. 向 /data/anr/traces.txt 写入各线程堆栈,并抓取 main、system_server 等关键日志。
  4. 通过 AppErrors 弹出“应用无响应”对话框,提供“等待”或“关闭”选项。
  5. 将 ANR 信息写入 dropbox 与 event log,供后续分析。

四、案例与误区复盘

案例:某应用在后台执行 BroadcastReceiver.onReceive() 时同步读取数据库,耗时约 15 秒。由于进程处于后台,系统按 60 秒阈值判定,未立即 ANR;但当用户再次打开应用,进程被提升为前台,后续广播按 10 秒阈值判定,立即触发 ANR。这说明“同一段代码是否 ANR”取决于进程状态与阈值。

常见误区:

  • 误区一:认为所有 ANR 都是 5 秒。实际上 Input 是 5 秒,Broadcast 前台 10 秒、后台 60 秒,Service 前台 20 秒、后台 200 秒,ContentProvider 10 秒。
  • 误区二:只看主线程堆栈。ANR 可能由 Binder 阻塞、锁竞争、系统服务卡顿引起,需要结合 traces.txt 中多线程堆栈与 system_server 状态分析。
  • 误区三:忽略 dropbox 与 event log。它们记录了 ANR 发生时的 CPU、内存、进程状态,是定位系统级卡顿的关键证据。
注意:ANR 判定不是“主线程卡住就报”,而是“在特定组件路径上超时且未完成回调”。因此优化 ANR 时,必须区分组件类型,分别针对 Input 分发、广播接收、Service 生命周期、ContentProvider 发布进行治理。

总结来看,四类 ANR 的触发机制可以归纳为:不同组件由不同系统服务计时,超时阈值随前后台状态变化,最终统一汇聚到 AMS 的 appNotResponding() 进行判定与报告。掌握这条链路,才能在日志与 traces 中快速定位是哪一个环节超时,从而对症下药。