一、ANR 概念与分类:你真的了解 ANR 吗?

做 Android 开发这些年,要说什么问题最让人头疼,ANR 算一个。很多同学一看到 “Application Not Responding” 这个弹窗,第一反应就是“完蛋,又出 bug 了”。

但你想过没有,ANR 的本质是什么?说白了,它是系统在跟你“要账”——你承诺在主线程做的事,没在规定时间内做完,系统就只能帮你掀桌子了。嗯,今天我们就把 ANR 这层窗户纸捅破。

1.1 什么是 ANR?

ANR,全称 Application Not Responding,就是“应用无响应”。这不是 crash,也不是 OOM。它更像系统的一个“监控值班员”——主线程如果被卡住太久,系统就会判定你“无响应”,然后给你一个弹窗警告,甚至直接杀掉进程。

我刚开始做 Android 开发那会儿,以为 ANR 只是偶发问题,直到有一次线上出了个严重的 ANR 故障,用户反馈 App 点了没反应,才意识到这玩意儿有多要命。从那以后,我养成了一个习惯:只要涉及主线程的操作,我都要问自己一句——这条路径会不会卡住?

核心认知: ANR = 主线程 + 超时。 只要主线程在指定时间内没能处理完某个任务,ANR 就会发生。

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 阶段执行了耗时初始化,导致其他进程访问被卡住
个人习惯: 我拿到一个 ANR 问题后,第一件事就是去 log 里看“ANR in”后面的关键词,是 "Input dispatching timed out" 还是 "executing service"。只有先确定类型,才能针对性优化,否则就是瞎忙活。

1.3 ANR 的触发条件(那些你不知道的“隐藏雷区”)

你以为只有主线程做耗时操作才会 ANR?太天真了。我遇到过不少“看起来跟主线程无关”的场景,结果照样爆 ANR。

举个例子:主线程虽然没做耗时计算,但它正在等待一个锁,而这个锁被某个子线程持有了——子线程又在执行耗时任务。好了,主线程被死锁住了,ANR 来了。这就是我们常说的“锁等待导致的 ANR”。

再比如:主线程和 Binder 线程之间发生了资源竞争。你在主线程调用了一个 Binder 方法,结果对方处理慢,主线程等于被“间接阻塞”了。这个坑我在做跨进程通信时踩过,当时查了两天才定位到。

所以,不要只盯着“主线程有没有做耗时操作”。只要是让主线程无法及时响应的事件,都有可能触发 ANR。常见的触发路径包括:

  1. 主线程直接做耗时操作——网络、IO、大图解码、复杂计算
  2. 主线程发生死锁或锁等待——子线程占用了主线程需要的锁
  3. 主线程等待 Binder 响应——远程调用对端卡住了
  4. 主线程被系统回调阻塞——如 Activity.onCreate 里做了太多初始化
  5. 主线程消息队列被持续占用——比如循环 post Delayed 任务
我曾经踩过的坑: 有一次线上 ANR 率突然升高,排查后发现是一个第三方 SDK 在后台线程持锁,而主线程在 onResume 中调用了该 SDK 的某个 get 方法,直接被卡住。所以,别以为第三方 SDK 就靠谱,该加超时的地方一定要加。

1.4 一张图看懂 ANR 触发流程

讲到这里,我觉得有必要给 ANR 的整体触发逻辑画张图。别小看这张图,它能帮你快速建立排查思维——一旦出现 ANR,你就能顺着这条链去找问题源头。

ANR 触发流程与类型分类 主线程卡住 → ANR Input 分发超时 5s 内未处理输入事件 Service 执行超时 前台20s / 后台200s Broadcast 超时 前台10s / 后台60s Provider 超时 默认10s 输入 ANR 常见原因 • 主线程做网络/I/O 操作 • 主线程死锁等待 • 主线程被 Binder 阻塞 • 主线程大量 UI 布局刷新 Service ANR 常见原因 • 在 onStartCommand 做网络请求 • 在 onCreate 做初始化数据库 • 锁等待导致主线程被卡 • 后台任务未及时切换到子线程 广播 ANR 常见原因 • onReceive 中做耗时操作 • 多个广播排队导致累积超时 • 订阅了系统广播未及时处理 Provider ANR 常见原因 • 初始化阶段耗时操作 • 数据库操作未开启事务 • 多个进程同时访问竞争资源 一句话总结:ANR 不是 bug,是系统对主线程的“最后通牒” 识别类型 → 定位根因 → 耗时任务移至子线程 → 注意锁竞争和 Binder 阻塞

1.5 一些想跟你聊的心里话

说实话,ANR 这个问题,很多初学者会觉得“只有大厂才需要关注”。但我告诉你,就算只做一个简单的工具类 App,如果做不好主线程管理,一样会 ANR。差别只是频率高低罢了。

我曾经接手过一个项目,线上 ANR 率高达 3%。你想想看,100 个用户里就有 3 个遇到 ANR 弹窗,这用户体验得多差?后来我们花了整整两周,把每一个主线程上的耗时操作都揪出来,该异步的异步,该缓存的缓存,最后把 ANR 率降到了 0.1%。

这个经历让我明白一个道理:ANR 优化不是锦上添花,而是一个 App 体面的底线。