ANR 产生原理:主线程消息循环、超时触发与系统 Watchdog
说实话,很多同学一遇到 ANR 就慌。但如果你搞懂了它的底层机制,其实 ANR 就是 Android 在告诉你:「喂,你的主线程卡住了,该优化了。」
我们这一节就把 ANR 怎么产生的,一层一层剥开来看。
主线程消息循环机制——老生常谈但核心
Android 的主线程,也叫 UI 线程,本质上是一个无限循环的 消息泵。它通过 Looper + MessageQueue + Handler 这套经典组合,不断从队列里取消息、处理消息。
你想想看,每一帧绘制、每一次触摸事件、每一个 onClick 回调,都是插进这个消息队列里的一个任务。系统希望这些任务执行得足够快——每帧 16ms 左右。如果某个任务占住了主线程太久,后面的消息就会积压,UI 卡顿,最终触发 ANR。
Looper.loop()是主线程的死循环,调用MessageQueue.next()阻塞取消息。- 每个消息对应一个
target.dispatchMessage(),即 Handler 的 handleMessage。 - 如果 handleMessage 里做了耗时 I/O、复杂计算、同步网络请求,那这个循环就会被卡住。
我曾在项目中遇到过一个问题:某个 BroadcastReceiver 的 onReceive() 里直接做了数据库入库操作,数据量一大,ANR 就频繁出现。后来我换成 IntentService 或者 WorkManager,问题立马解决。说白了,就是不能把耗时活扔在主线程的消息队列里。
超时触发条件——系统在什么情况下“判你死刑”
系统不会因为你卡了一两秒就弹 ANR 对话框。它有一套严格的超时规则,不同场景的阈值不一样。
下面这张表是我个人习惯整理的,方便大家记忆:
| 场景 | 超时时间 | 典型表现 |
|---|---|---|
| Input 事件(触摸、按键) | 5 秒 | 点击后无响应,屏幕卡死 |
| BroadcastReceiver(前台) | 10 秒 | 收到广播后未及时 return |
| BroadcastReceiver(后台) | 60 秒 | 同上,但额度更宽 |
| Service(前台) | 20 秒 | onStartCommand 耗时过长 |
| ContentProvider | 10 秒 | provider 查询/更新慢 |
View.onTouchEvent() 里加了一个 Thread.sleep() 用来模拟点击防抖…… 千万别在输入事件里做任何阻塞操作,严格讲 5 秒内必须处理完并返回。
为什么会这样设计?因为系统需要保证用户每次操作都能在一两帧内得到反馈。5 秒相当于 300 帧的积压,用户早就不耐烦了。所以这个阈值其实是「容忍极限」。你别想着刚好卡在 4.9 秒,超了就超了。
系统服务 Watchdog——幕后黑手
除了主线程自己的消息循环超时,Android 系统内部还有一个更底层的 Watchdog 守护进程。
这个 Watchdog 运行在 system_server 进程中,它每隔一段时间(默认 30 秒)去检查一些系统关键服务(比如 ActivityManagerService、WindowManagerService、PackageManagerService 等)的锁是否正常释放。如果某个服务被死锁住了,超过 60 秒都没响应,Watchdog 会直接杀死 system_server,然后触发系统重启。
Binder 调用卡住了某个系统服务的关键锁,那 Watchdog 同样会将 App 连带杀掉。这就是为什么有些 ANR 日志里会出现「Watchdog: Blocked in monitor...」字样。
嗯,这里要注意。我们平时看到的「ANR in <...>」只是应用层的主线程超时。而 Watchdog 引发的往往是整个系统重启,重启后出现「System UI 已停止运行」之类的弹窗。
我记得有次线上问题:一个第三方 SDK 在 onBind 里做了死循环,导致 ServiceManager 的锁一直被占用。系统 Watchdog 发现后,直接把 system_server 干掉了,所有 App 都挂了。最后排查了三天才定位到那个 SDK…… 从那以后我对第三方库的源码审查就格外严了。
核心流程一览
下面这张图是我自己画的,把上面提到的几个关键节点串起来了。你能清晰地看到 ANR 从「消息循环卡住」到「超时检测」再到「Watchdog 介入」的完整链路。
从上图你可以看到,主线程如果处理消息太慢,就会进入超时检测流程,超过阈值就弹 ANR。而 Watchdog 是另一条线,它专门盯着系统服务,一旦发现死锁,直接暴力重启。
作为开发者,我们最常碰到的是左边那条线——主线程被耗时操作卡住。所以要避免 ANR,核心思路就是:别在 UI 线程做任何可能超过几十毫秒的事情。
好了,这一节我们深入分析了 ANR 产生的三个核心机制。理解这些原理,是后面我们做 Room 数据库调优的基础——因为很多数据库操作如果不注意,就会成为主线程消息循环里的那颗「毒瘤」。