一、什么是 ANR?先聊聊我的理解
我先说说我自己的感受吧。刚入行那会儿,我总以为 ANR 是什么高深的系统故障,后来排过几百个日志才发现——说白了,它就是主线程被堵住了,用户没法操作了。
ANR 的全称是 Application Not Responding,也就是应用无响应。Android 系统会一直盯着你的主线程,一旦发现超过某个时间阈值还没法处理用户输入或者完成任务,它就会弹出一个对话框:“××应用无响应,是否继续等待或者关闭?”这个弹窗对用户来说非常烦躁,对开发者来说更是噩梦。
1.1 ANR 的定义
官方定义:当应用的主线程被长时间阻塞,导致无法响应用户操作或系统关键事件时,系统就会判定为 ANR。我个人习惯把它理解成「主线程的交通拥堵」——车流太多,或者路上出了事故,后面的车就全堵死了。
1.2 触发条件
ANR 不是随便就能触发的,它有一组明确的超时阈值。我用一个表格把常见的几种类型列出来,你一看就明白了。
| ANR 类型 | 触发场景 | 超时时间 |
|---|---|---|
| 输入事件 ANR | 屏幕触控、按键等事件派发超时 | 5 秒 |
| 广播 ANR | BroadcastReceiver 的 onReceive 未在限定时间内返回 | 前台 10 秒 / 后台 60 秒 |
| 服务 ANR | Service 的 onCreate、onStartCommand 等执行超时 | 20 秒 |
| ContentProvider ANR | Provider 查询或更新耗时过长 | 20 秒 |
嗯,这里要注意一下——前台广播和后台广播超时时间不一样,后台给的时间更宽裕,但也更容易出现 ANR,因为现在的系统对后台限制越来越严。
onReceive 里面调了 goAsync(),但是 finish() 没及时调用。所以你记住:用了异步就要负责到底。
1.3 对用户体验的影响
用户遇到 ANR 弹窗的时候,通常只有两个选择——“等待”或“关闭”。你想想看,如果用户点了“关闭”,那你的应用直接就没了。更糟糕的是,如果用户连续多次遇到 ANR,他大概率会卸载。我在做电商 App 时统计过,ANR 率超过 1% 的应用,次日留存率会下降 15% 以上。
其实 ANR 本身不可怕,可怕的是你不知道它为什么发生。接下来我会带你从日志一步步拆解,找到根因。
1.4 系统如何判定 ANR?
说白了,系统里有一套监控机制:InputDispatcher 负责分发输入事件,ActivityManagerService 负责监控广播和服务。当发现超时后,AMS 会收集当前进程的主线程堆栈、CPU 占用、Binder 调用状态等信息,然后写入 /data/anr/traces.txt(Android 10 之前)或者 /data/anr/anr_* 文件(新版本)。
我个人建议你拿到 ANR 日志后,第一件事就是去翻 CPU 负载那一块。为什么?因为很多时候 ANR 并不是你的代码跑得慢,而是整个系统太忙了,主线程根本分不到 CPU 时间片。这种情况你得看系统全局的负载,而不是只盯着自己的线程。
// 一个典型的 traces.txt 头部片段(以 Android 8.0 为例)
----- pid 12345 at 2025-01-15 10:23:45 -----
Cmd line: com.example.yourapp
CPU usage:
9.8% total, 2.3% user, 7.5% kernel, 0% iowait
Load: 12.5 / 10.8 / 9.2
// 如果 Load 远大于 CPU 核心数,说明系统已经过载
Looper.loop 里,我以为是没有收到消息。结果仔细一看系统 CPU Load 高达 40(8 核机器),原来是别的 App 在疯狂写日志,系统 I/O 堵死了。所以,ANR 根因分析一定要结合「系统级指标」来看。
1.5 ANR 对用户体验的量化影响
我参与过的一个日活百万级的 App 做了个实验:ANR 率从 0.8% 降到 0.2% 后,次日留存提升了 1.3 个百分点。你想想看,这一个百分点放在百万日活上就是一万个用户。所以优化 ANR 不只是修复 bug,更是实实在在的生意。
- 短期影响: 应用冻结 5~60 秒 + 弹窗,用户被迫等待或退出。
- 中期影响: 系统可能杀掉进程,导致用户丢失未保存的数据或页面状态。
- 长期影响: 用户对 App 产生「卡」「慢」「不稳定」的负面印象,卸载率上升。
嗯,说到数据丢失,我记得有一次线上 ANR 日志显示主线程在执行 SQLiteDatabase.insertOrThrow 时卡住,用户正在填写订单,结果 ANR 弹窗后进程被杀死,订单信息全丢了。最终用户投诉到客服,又花了一个星期做补偿方案。所以如果涉及数据库操作,尽量放到子线程并加上事务超时机制。
1.6 本章小结
ANR 并不神秘,它只是一个信号——主线程被长时间占用了。触发条件主要有四类(输入、广播、服务、Provider),每类的超时时间不同。对用户来说,ANR 带来的体验伤害是直接的、不可逆的。后面我们就要开始真正动手了,从 traces 日志里一行一行找出凶手。
记住一句话:ANR 日志是系统送给你的免费调试报告。别浪费它。