第一章:ANR 简介与原理归纳总结
ANR 这三个字母,搞 Android 开发的应该都不陌生。Application Not Responding,说白了就是应用没反应了。用户点一下屏幕,等半天界面还是僵在那,系统就会弹出一个「应用无响应」的对话框。嗯,那种感觉挺糟的,用户大概率直接给个差评。
我在项目中遇到过很多次 ANR,早期甚至被线上崩溃率搞到头皮发麻。当时我们团队的一个重要模块,每天 ANR 率超过 3%。后来经过几个月治理,降到了 0.1% 以下。所以第一节课,咱们先把 ANR 的「根」说清楚:它到底是什么?背后的原理是怎样的?只有把原理吃透了,后续治理才能有的放矢。
一、ANR 的定义
官方定义:当主线程(UI 线程)被长时间阻塞,无法处理用户输入或消息时,系统就会判定为 ANR。但实际上,ANR 不光是主线程卡顿,还包括一些系统服务超时。我个人习惯把 ANR 理解成:Android 系统对应用响应能力的一次「超时检查」。
触发时间点一般有这几种:
- 输入事件超时:比如触屏、按键等事件,5 秒内没被处理。
- 广播接收器超时:前台广播 10 秒、后台广播 60 秒没执行完 onReceive。
- 服务超时:Service 的 onCreate/onStartCommand/onBind 等 20 秒没返回。
- ContentProvider 超时:ContentProvider 的 publish 操作超过 10 秒。
核心要点:ANR 的本质是「消息处理超时」,消息的来源可以是用户、系统组件或者 Binder 调用。
二、原理:主线程与消息循环
理解 ANR,必须先理解 Android 的消息循环机制。主线程从创建起就持有一个 Looper,不断从 MessageQueue 里取出消息并执行。如果某个消息执行时间过长,后续消息就会被积压。这时候如果来了一个 Input 事件(比如点击),系统会启动一个 5 秒的定时器。要是 5 秒内主线程还没处理完前一个消息,定时器就炸了——ANR 发生。
为什么会这样?你需要知道系统检测 ANR 的底层逻辑。Android 会为每个应用进程注册一个 AppNotRespondingDialog 的监控,利用 Binder 驱动和 Watchdog 线程来轮询。我截取一段关键源码路径:
// frameworks/base/services/core/java/com/android/server/am/AppNotRespondingDialog.java
// 核心检测逻辑在 ActivityManagerService 中
void appNotResponding(ProcessRecord app, ...) {
// 更新 ANR 计数器
// 收集 CPU 使用率、堆栈信息
// 弹出 ANR 对话框或直接杀进程
}
我个人习惯把这个过程画成一个流程图,方便理解:
三、ANR 的类型与超时阈值
不同组件的 ANR 超时阈值不一样,这直接决定了你怎么去排查问题。我列个表,你们可以保存下来:
| ANR 类型 | 超时时间 | 触发条件 |
|---|---|---|
| Input 事件 ANR | 5 秒 | touch / key 等事件未在 5 秒内分发到主线程并处理完 |
| 前台广播 ANR | 10 秒 | onReceive 方法执行超过 10 秒(包括异步广播) |
| 后台广播 ANR | 60 秒 | onReceive 方法执行超过 60 秒 |
| Service 超时 ANR | 20 秒 | onCreate / onStartCommand / onBind 等超过 20 秒 |
| ContentProvider 超时 | 10 秒 | ContentProvider 的 publish 操作超过 10 秒 |
我曾经踩过一个坑:后台广播的 60 秒并不代表「完全安全」。如果广播接收器中做了大量 IO 操作,即便没超时,也可能因为频繁 GC 导致主线程 jank。所以「不超时」不等于「不卡顿」。
四、ANR 的本质原因归纳
抛开各种花哨的外壳,ANR 的本质说白了就两个原因:
- 主线程做了耗时的操作——比如网络请求、数据库大量读写、大文件 IO、复杂计算等。
- 主线程被锁阻塞了——比如死锁、长时间持有锁等待、Binder 调用同步超时等。
你想想看,主线程本来就应该只做 UI 更新和轻量操作。一旦把耗时任务丢到主线程,或者锁竞争激烈,ANR 就离你不远了。
我个人习惯把 ANR 治理分成三层:监控层(知道哪里 ANR) → 分析层(定位根因) → 修复层(改代码/优化锁)。后面章节我们会逐个击破。
五、避坑指南:我遇到过的最隐蔽 ANR
嗯,这里要特别说一个我早年的教训。曾经有一个模块,在 onResume 里调用了 View.getDrawingCache() 并直接在主线程写文件。线上 ANR 率飙升到 2%,但日志里只显示主线程卡在 nativeRead。查了半天才发现是文件系统 IO 阻塞。后来我们把写文件放到 AsyncTask 里,ANR 直接清零。所以 任何可能涉及 IO、网络、锁的地方,都要警惕主线程。
六、本章总结
我们这一章把 ANR 的「骨架」搭起来了:
- ANR 是主线程消息处理超时的结果。
- 主线程的 Looper 循环一旦被长时间阻塞,系统 Watchdog 就会触发。
- 不同组件有不同超时阈值,需要区别对待。
- 本质原因只有两个:耗操作和锁阻塞。