第一章: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 触发原理流程 主线程 Looper 循环 处理消息(UI 更新/触摸事件) 消息处理耗时 > 5s Watchdog 检测到超时 收集主线程堆栈 弹出 ANR 对话框 说明 正常情况下,消息处理完成后立即返回 Looper 循环。 当某个消息处理超过 5 秒,系统 Watchdog 就会触发 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 的本质说白了就两个原因:

  1. 主线程做了耗时的操作——比如网络请求、数据库大量读写、大文件 IO、复杂计算等。
  2. 主线程被锁阻塞了——比如死锁、长时间持有锁等待、Binder 调用同步超时等。

你想想看,主线程本来就应该只做 UI 更新和轻量操作。一旦把耗时任务丢到主线程,或者锁竞争激烈,ANR 就离你不远了。

我个人习惯把 ANR 治理分成三层:监控层(知道哪里 ANR) → 分析层(定位根因) → 修复层(改代码/优化锁)。后面章节我们会逐个击破。

五、避坑指南:我遇到过的最隐蔽 ANR

嗯,这里要特别说一个我早年的教训。曾经有一个模块,在 onResume 里调用了 View.getDrawingCache() 并直接在主线程写文件。线上 ANR 率飙升到 2%,但日志里只显示主线程卡在 nativeRead。查了半天才发现是文件系统 IO 阻塞。后来我们把写文件放到 AsyncTask 里,ANR 直接清零。所以 任何可能涉及 IO、网络、锁的地方,都要警惕主线程。

六、本章总结

我们这一章把 ANR 的「骨架」搭起来了:

  • ANR 是主线程消息处理超时的结果。
  • 主线程的 Looper 循环一旦被长时间阻塞,系统 Watchdog 就会触发。
  • 不同组件有不同超时阈值,需要区别对待。
  • 本质原因只有两个:耗操作和锁阻塞。