ANR触发原理:系统如何判定你的App"不响应"?

做ANR链路追踪,第一步得搞清楚——系统到底凭什么判定一个App挂了?这不是玄学,是Android系统里一套严格的超时+监控机制。

我记得刚接触ANR时,觉得它就是个弹窗提示。后来踩过几次坑才发现,背后是AMS(ActivityManagerService)和WMS(WindowManagerService)两套调度系统在实时盯着你的线程。一旦超时,直接给你"定罪"。

核心概念: ANR = Application Not Responding。触发条件是:主线程在特定时间内没有完成某个关键操作。

2.1 Android系统的ANR机制

系统怎么知道你卡住了?说白了,是通过消息队列超时检测。Android主线程本质是一个Looper循环,不断从MessageQueue取消息执行。如果某个消息处理太久,Looper就不再向Binder驱动发送心跳,系统就会认为"没响应"。

但实际检测更复杂。系统用了三套独立的超时监控器,分别对应不同的场景。我个人习惯把它们分为三类:

  1. InputDispatching Timeout(输入调度超时)——用户触摸、按键发到WMS,WMS等待App处理完成并反馈。
  2. Broadcast Timeout(广播超时)——AMS发送广播后,等待Receiver的onReceive()执行完成。
  3. Service Timeout(服务超时)——AMS启动Service后,等待其onCreate()或onStartCommand()返回。

你想想看,这三者本质都是"主线程忙不过来了"。但判定逻辑和超时阈值完全不一样,后文会详细讲。

避坑: 我曾经见过一个App,ANR率很高但怎么都复现不了。后来抓了systrace发现,是主线程在等某个ContentProvider的同步锁。说白了,不是主线程在"卡",是它在"等"——但系统不区分这两种情况,一律算ANR。

2.2 AMS与WMS的决策流程

ANR判定不是简单倒计时。它是一套两段式决策流程:先由AMS或WMS发超时消息,再由系统进程回应"是否恢复"。我们来拆开看。

2.2.1 WMS:输入事件的ANR判定

用户手指一点,事件从驱动→InputReader→InputDispatcher→App主线程。WMS里的InputDispatcher会给App设一个超时计时器(默认5秒)。如果App在规定时间内没通过Binder通知"事件处理完毕",WMS就会上报ANR。

这里有个细节:只有输入事件才会触发WMS判定。如果App只是后台跑任务,不涉及输入,WMS就不管。

2.2.2 AMS:广播与服务超时

AMS扮演的是更宏观的监控者。它发送广播后,会启动一个Handler延时消息来模拟超时。如果Receiver在规定时间(前台广播10秒,后台广播60秒)内没有通过finishReceiver()通知完成,AMS就认为超时。

Service同理。启动Service时,AMS会设置一个超时(前台服务20秒,后台服务200秒)。如果Service的onCreate()或onStartCommand()没返回,就触发ANR。

关键点: AMS和WMS各自独立维护超时队列。它们都会往主线程投递一个"检查恢复"的消息。如果主线程响应了这个消息,说明Looper还在工作,但可能消息处理太慢。实际上,ANR的直接原因永远是主线程被某个消息阻塞。

2.2.3 决策流程图

AMS与WMS的ANR决策流程 用户操作 / 系统广播 WMS InputDispatcher AMS Handler 超时 设置5秒输入超时 设置广播/服务超时 主线程完成? (Binder回调) 取消定时器 触发ANR弹窗 完成 超时 主线程若被阻塞,则Binder回调无法送达 -> 超时 -> ANR

2.3 超时时间阈值

阈值是多少?我直接列个表,建议你记下来。面试常考,压测排查也用得上。

场景 前台(秒) 后台(秒) 备注
输入事件(触摸/按键) 5 5 WMS判定,无前后台区分
BroadcastReceiver (有序) 10 60 从onReceive()开始计时
ContentProvider超时 10 10 首次query()创建耗时
Service启动 20 200 onCreate/onStartCommand
DexClassLoader/热修复 15 15 系统内部阈值,不暴露
注意: 后台阈值虽然长(200秒),但千万别以为可以慢。主线程阻塞200秒意味着App已经死了。我经历过一个线上案例:低端机后台Service做数据库迁移,超时后系统直接杀进程,用户数据没写完,导致重启后crash。这就是数据完整性被破坏的典型场景。

2.4 超时后的内部动作

触发ANR后系统做了什么?嗯,这里要注意:并不是立刻弹窗。系统会先做一系列"举证"动作:

  1. Dump主线程堆栈——写入/data/anr/traces.txt或traces_xx.anr
  2. Dump所有线程堆栈——包含native线程、Binder线程
  3. 收集内存信息——global reference count、锁信息等
  4. 写入dropbox——供开发者拉取分析
  5. 弹ANR对话框(仅前台App)或直接杀掉进程(后台App)

你会发现,那个对话框只是结果。真正有价值的数据在traces.txt里。我每次排查ANR第一件事就是拉这只"尸体"。

2.5 系统源码里的关键类

如果你喜欢看源码,记住这几个类:

  • com.android.server.wm.InputDispatcher —— WMS的输入超时逻辑
  • com.android.server.am.ActivityManagerService —— 广播/服务超时处理
  • com.android.server.am.AppNotRespondingDialog —— 弹窗生成
  • com.android.server.am.AnrMonitor (Android 12+) —— 统一ANR监控
个人技巧: 我习惯在InputDispatcher的notifyANR()处打日志点,配合dumpsys window input看当前输入队列是否堵塞。这招在定位"触摸无响应但没弹框"的场景特别好用。

2.6 小结:ANR的本质是"主线程调度超时"

不管是输入、广播还是Service,系统判断的依据都一样:主线程在规定时间内没有完成应该做的事。背后是AMS和WMS两套独立计时器,它们共享一个前提——主线程的Looper必须能处理Binder回调。

所以,当你看到ANR时,别只盯着"卡顿"。先问自己:是哪条超时规则被触发了? 回答了这个问题,你就找到了断案的第一个线索。

下节预告 (不输出,仅占位)