## ANR 概述 ### 什么是 ANR ANR 的全称是 **Application Not Responding**,也就是「应用无响应」。说白了,就是你的 App 卡死了,系统在等了很久之后,觉得你再也活不过来了,于是弹出那个烦人的对话框:「XX 应用无响应,要关闭吗?」 我做性能优化这么多年,遇到的 ANR 问题少说也有上百个。印象最深的一次,是用户反馈说某个支付页面卡死,点确认按钮没反应。我一看日志,嘿,主线程在执行一个 Keystore 签名操作,直接卡了 8 秒。这不 ANR 才怪。 ANR 的本质其实是 Android 系统的一种**自我保护机制**。它监控着主线程(UI 线程)的响应情况,一旦发现某个事件超时未被处理,就会判定应用无响应。你可以理解为:系统在帮你盯着你的主线程,别让它偷懒太久。 >
核心要点:ANR 是系统级的监控,只盯着主线程,子线程随便卡多久都不会直接触发 ANR(但会让你的 App 体验极差)。
### ANR 触发条件 为什么会有 ANR?系统到底是什么时候判定「你死了」?我归纳起来其实就是四种场景: 1. **输入事件超时**:用户在屏幕上点了一下,或者按了物理键,系统把事件分发给你的 Activity,但过了 5 秒你没处理。系统会想:「这 App 是不是卡死了?」 2. **广播接收器超时**:你的 `BroadcastReceiver` 在 `onReceive` 里做耗时操作,前台广播超时 10 秒,后台广播超时 60 秒。我曾经有个同事在广播里直接做了网络请求——嗯,你懂的。 3. **服务超时**:`Service` 的 `onStartCommand` 或 `onBind` 等生命周期方法,如果执行超过 20 秒,也会触发 ANR。 4. **ContentProvider 超时**:这种情况相对少些,但如果在 `ContentProvider` 的 `query` 或 `insert` 里做了太重的操作,同样会触发,超时时间默认 10 秒。 你看,核心就一句话:**主线程被堵死了,而且堵的时间超过了系统设定的阈值**。 >
我的经验:很多 ANR 其实不是代码逻辑错误,而是「不该在主线程做的事」被放在了主线程。比如文件读写、网络请求、数据库操作、Keystore 加解密——嗯,这正是咱们这门课重点要讲的内容。
### Android 不同组件的 ANR 超时时间 不同组件对「你能卡多久」的容忍度是不一样的。我整理成了一张表格,方便你对照: | 组件类型 | 超时时间 | 触发场景 | |---------|---------|---------| | Activity | 5 秒 | 输入事件(按键、触摸)未在 5 秒内处理 | | BroadcastReceiver(前台) | 10 秒 | `onReceive` 方法执行超过 10 秒 | | BroadcastReceiver(后台) | 60 秒 | 同上,后台广播放宽到 60 秒 | | Service | 20 秒 | `onStartCommand`/`onBind` 等生命周期方法超时 | | ContentProvider | 10 秒 | `query`/`insert`/`update`/`delete` 操作超时 | | InputEvent(输入事件) | 5 秒 | 分发给应用后未及时消费 | >
特别注意:这些时间是 Android 原生的默认值。不同厂商的 ROM 可能会 tweak(调整)这些阈值。我曾经碰到过某厂商把 Activity 超时改成了 3 秒,导致同一段代码在其他手机正常,在那台手机就疯狂 ANR。所以不要过分依赖默认值,代码质量才是根本。
### 为什么会卡死?ANR 的典型原因 我大概总结了最常见的几类 ANR 原因,你在排查时可以从这几个方向入手: - **主线程直接做耗时操作**:网络请求、大文件 IO、数据库批量写入、Bitmap 解码等。 - **锁竞争导致的死锁/长时间等待**:主线程等一个锁,而持有锁的子线程也卡住了。 - **Binder 调用阻塞**:比如系统服务(ActivityManager、WindowManager)响应慢,主线程在等 Binder 返回。 - **Keystore 操作阻塞**:这正是我们这门课要深入分析的。Keystore 涉及到硬件安全模块(TEE)的交互,有时候一次签名耗时能到几秒钟,如果放在主线程,ANR 几乎必然发生。 我自己经历过一个经典案例:应用启动时要从 Keystore 里读取一个私钥,然后做登录签名。这个操作在低端机上用了整整 6 秒。你想想看,启动界面卡 6 秒——用户不卸载才怪。 ### ANR 的全景视图 为了帮你快速建立认知,我画了一张图,把 ANR 的整体知识结构串起来: ANR 核心知识体系 ANR 应用无响应 触发条件 组件超时时间 典型原因 输入事件超时(5s) 广播接收器超时(10s/60s) Service 超时(20s) ContentProvider 超时(10s) Activity: 5s Broadcast: 10s/60s Service: 20s ContentProvider: 10s 主线程耗时操作 锁竞争/死锁 Binder 调用阻塞 Keystore 操作阻塞(重点!) 🔑 课程聚焦 Keystore 操作是 ANR 中容易被忽略的「隐藏杀手」 后续章节将深入分析根因并给出实战解决方案 ### 怎么监控 ANR? 很多人问:ANR 发生了我怎么知道?总不能每次都等用户骂吧。其实 Android 提供了几种手段: - **logcat 日志**:出现 ANR 时,系统会输出 `ANR in ...` 的日志,并附带 CPU 使用率和堆栈信息。我记得有次 DEBUG,直接看到主线程卡在 `KeyStore.get` 方法里。 - **/data/anr/traces.txt**:这是 ANR 的「尸体解剖报告」,记录了所有线程的堆栈。我建议你每次 ANR 后第一时间抓这个文件。 - **第三方监控平台**:像 Firebase、Bugly、Sentry 等,都能捕获 ANR 并上报堆栈。我一般习惯用 Bugly,因为它可以区分主线程卡顿和 ANR,比较精准。 >
避坑指南:曾经我接到一个线上 ANR,堆栈指向了 `Thread.sleep()`,但代码里根本没有 sleep。后来发现是某个第三方 SDK 在子线程里用了 `Looper.loop()` 作死。所以看到堆栈别急着改代码,先定位到底哪个线程在占用主线程的资源。
### 一个小练习 你现在可以尝试在手机上运行一个简单的测试:在 `Activity.onCreate` 里直接调用 `Thread.sleep(6000)`,然后启动 App,看看发生了什么。注意,这只是演示用的,千万不要在生产代码里这么干。 ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { Thread.sleep(6000); // 模拟耗时操作,会触发 ANR } catch (InterruptedException e) { e.printStackTrace(); } } ``` 运行之后,你会发现大概 5 秒左右,屏幕变灰,弹出了 ANR 对话框。这就是今天我们要解决的核心问题——如何避免你的应用出现这种尴尬的局面。 第一章内容就到这里。ANR 的本质你已经清楚了,接下来我们会重点剖析 Keystore 操作如何引发 ANR,以及如何从根源上避免它。