一、ANR 概念与分类:你真的了解 ANR 吗?
做 Android 开发这些年,要说什么问题最让人头疼,ANR 算一个。很多同学一看到 “Application Not Responding” 这个弹窗,第一反应就是“完蛋,又出 bug 了”。
但你想过没有,ANR 的本质是什么?说白了,它是系统在跟你“要账”——你承诺在主线程做的事,没在规定时间内做完,系统就只能帮你掀桌子了。嗯,今天我们就把 ANR 这层窗户纸捅破。
1.1 什么是 ANR?
ANR,全称 Application Not Responding,就是“应用无响应”。这不是 crash,也不是 OOM。它更像系统的一个“监控值班员”——主线程如果被卡住太久,系统就会判定你“无响应”,然后给你一个弹窗警告,甚至直接杀掉进程。
我刚开始做 Android 开发那会儿,以为 ANR 只是偶发问题,直到有一次线上出了个严重的 ANR 故障,用户反馈 App 点了没反应,才意识到这玩意儿有多要命。从那以后,我养成了一个习惯:只要涉及主线程的操作,我都要问自己一句——这条路径会不会卡住?
1.2 ANR 的四种类型(话说,你真的分得清吗?)
很多人以为 ANR 只有一种,其实它分为四大类:Input、Service、Broadcast 和 Provider。每种类型的触发场景和超时阈值都不一样。我见过不少同学把 Service 超时当成 Input 超时去排查,浪费了大量时间。所以,先把这四种类型搞清楚,比啥都重要。
| ANR 类型 | 触发场景 | 超时时间 | 典型案例(我踩过的坑) |
|---|---|---|---|
| Input 分发 ANR | 主线程 5s 内未处理完当前输入事件 | 5 秒 | 列表加载大量图片时点击闪退,主线程被 UI 绑定阻塞 |
| Service 后台 ANR | Service 的 onCreate / onStartCommand 执行超时 | 前台服务 20s / 后台服务 200s | 我在某直播项目中,onStartCommand 里直接做了网络请求,直接爆了 |
| Broadcast 广播 ANR | onReceive 执行超时 | 前台广播 10s / 后台广播 60s | 一次系统闹钟广播里做数据库读写,结果线上 ANR 率直接飙升 |
| Provider 内容提供者 ANR | Provider 的创建或操作超时 | 默认 10 秒 | ContentProvider 中 init 阶段执行了耗时初始化,导致其他进程访问被卡住 |
1.3 ANR 的触发条件(那些你不知道的“隐藏雷区”)
你以为只有主线程做耗时操作才会 ANR?太天真了。我遇到过不少“看起来跟主线程无关”的场景,结果照样爆 ANR。
举个例子:主线程虽然没做耗时计算,但它正在等待一个锁,而这个锁被某个子线程持有了——子线程又在执行耗时任务。好了,主线程被死锁住了,ANR 来了。这就是我们常说的“锁等待导致的 ANR”。
再比如:主线程和 Binder 线程之间发生了资源竞争。你在主线程调用了一个 Binder 方法,结果对方处理慢,主线程等于被“间接阻塞”了。这个坑我在做跨进程通信时踩过,当时查了两天才定位到。
所以,不要只盯着“主线程有没有做耗时操作”。只要是让主线程无法及时响应的事件,都有可能触发 ANR。常见的触发路径包括:
- 主线程直接做耗时操作——网络、IO、大图解码、复杂计算
- 主线程发生死锁或锁等待——子线程占用了主线程需要的锁
- 主线程等待 Binder 响应——远程调用对端卡住了
- 主线程被系统回调阻塞——如 Activity.onCreate 里做了太多初始化
- 主线程消息队列被持续占用——比如循环 post Delayed 任务
1.4 一张图看懂 ANR 触发流程
讲到这里,我觉得有必要给 ANR 的整体触发逻辑画张图。别小看这张图,它能帮你快速建立排查思维——一旦出现 ANR,你就能顺着这条链去找问题源头。
1.5 一些想跟你聊的心里话
说实话,ANR 这个问题,很多初学者会觉得“只有大厂才需要关注”。但我告诉你,就算只做一个简单的工具类 App,如果做不好主线程管理,一样会 ANR。差别只是频率高低罢了。
我曾经接手过一个项目,线上 ANR 率高达 3%。你想想看,100 个用户里就有 3 个遇到 ANR 弹窗,这用户体验得多差?后来我们花了整整两周,把每一个主线程上的耗时操作都揪出来,该异步的异步,该缓存的缓存,最后把 ANR 率降到了 0.1%。
这个经历让我明白一个道理:ANR 优化不是锦上添花,而是一个 App 体面的底线。