ANR 基本概念与分类
说起 ANR,很多刚入行的同学第一反应是「啊,那个弹窗又来了」。我记得自己刚接触 Android 开发时,第一次遇到 ANR 完全懵了——点了一个按钮,屏幕就卡住,然后弹出“应用无响应”的对话框。当时我还以为是手机坏了。后来才明白,这是系统在向你喊话:你的应用主线程堵死了。
ANR 的全称是 Application Not Responding。说白了,就是系统在规定时间内没有得到主线程的回应,觉得你的 App 已经“死”了,于是弹个框让用户选择继续等还是直接关掉。这个机制其实很合理——你想想看,如果每个 App 都可以任性地在主线程上做耗时操作,那用户体验简直没法要。
系统怎么判定 ANR?
Android 系统有一套监控机制,专门盯着主线程。不同场景的“超时阈值”不一样,我整理了一下:
| ANR 类型 | 超时时间 | 触发条件 |
|---|---|---|
| Input ANR | 5 秒 | 输入事件(按键、触摸)未在 5 秒内分发并处理完毕 |
| Service ANR | 20 秒 | Service 的 onCreate/onStartCommand/onBind 等方法执行超过 20 秒 |
| Broadcast ANR | 10 秒 | BroadcastReceiver 的 onReceive 方法执行超过 10 秒(前台广播) |
| Provider ANR | 20 秒 | ContentProvider 发布超时或查询/插入操作超过 20 秒 |
嗯,这里要注意:前台广播的超时阈值是 10 秒,后台广播是 60 秒。我在项目中见过不少同学以为广播只有 10 秒限制,结果后台广播一跑就是半分钟,照样触发 ANR。
四大 ANR 分类详解
1. Input ANR
最常见的一种。当你点击屏幕或按键后,系统把事件投递到主线程的消息队列,但主线程正忙着做别的事(比如在执行一个大型 JSON 解析、操作数据库),5 秒内没轮到这个事件,ANR 就来了。我曾经在一个视频播放器项目里遇到过:用户滑动进度条时界面卡死,最后发现是缩略图生成全堆在主线程上。
2. Service ANR
Service 的生命周期方法(onCreate、onStartCommand、onBind)默认跑在主线程。如果你在里面直接执行网络请求或者写大文件,20 秒一到必然弹 ANR。我习惯的做法是:所有耗时操作都扔到子线程或者用 IntentService。对了,如果 Service 是前台服务,系统会给更严格的限制。
3. Broadcast ANR
广播接收器的 onReceive 方法同样跑在主线程。这里有个坑:很多开发者以为广播处理很快,顺手就在里面写了个 SharedPreferences 的 commit() 操作,但 commit() 是同步写磁盘的,磁盘 I/O 一慢就可能超时。而且如果广播是动态注册的,在 onReceive 里 new 一个线程去干活,但方法本身很快就返回了,这不会 ANR。但 如果你在 onReceive 里调用了 goAsync(),那后台任务的执行时间依然受广播超时限制。
4. Provider ANR
ContentProvider 的增删改查方法也跑在主线程。我在一个跨进程数据共享的应用里踩过这种坑:一个外部应用调了我们的 Provider 查询接口,结果查询语句没优化,全表扫描用了十几秒,直接导致我们的 Provider ANR。记住:Provider 的 CRUD 必须异步,尤其是跨进程调用时,调用方会阻塞等待。
ANR 对用户体验的“杀伤力”
弹出一个“应用无响应”对话框,用户只有两个选择:
- 等待——如果继续卡住,过一会可能系统直接弹出“强行停止”选项。
- 关闭——用户一旦点了“确定”,应用进程就被系统杀掉了。
不管是哪种选择,用户都会对你的 App 产生负面印象。更严重的是,ANR 发生后,系统会记录 /data/anr/traces.txt 文件,如果短时间内多次 ANR,甚至会被系统加入“坏应用”列表,后续启动可能被限制。我见过一个 App 因为频繁 ANR 导致 Google Play 下架的案例——那才叫惨烈。
核心要点
ANR 的本质是主线程超时未响应。
四种类型对应不同的超时阈值和场景。
避坑:任何耗时操作都别碰主线程。
个人小技巧
调试时我经常用 adb shell dumpsys dropbox --print data_app_anr 快速抓取最近的 ANR 日志,比你去 /data/anr 下翻文件方便多了。
曾经踩过的坑
我曾经在广播接收器里用 SharedPreferences.edit().commit() 导致 ANR。后来全改成 apply(),异步写入,问题解决。记住:commit() 是同步刷盘,apply() 是异步。
知识结构图:ANR 全景
这张图把整个 ANR 体系串起来了。你记住一个核心:所有 ANR 的根因都是主线程被阻塞。不管你是做输入事件、服务、广播还是内容提供者,只要触碰了主线程红线,系统就会翻脸。下一段我们来聊怎么从 traces 文件里揪出真凶——那个才是真正的硬核内容。