核心要点: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 发生了我怎么知道?总不能每次都等用户骂吧。其实 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,以及如何从根源上避免它。