ANR 的四种类型:Input、Broadcast、Service、Provider
这一章我们聊聊 ANR 的具体分类。说白了,系统给每个组件都设了个“超时闹钟”。不同类型的 ANR,触发场景、超时时间、排查思路都不一样。
我个人习惯把 ANR 类型当作“倒计时”来看。主线程没在规定时间内干完活,系统就认为它 “Not Responding” 了。一共就四类:Input 事件、BroadcastReceiver、Service、ContentProvider。
1. Input 事件超时(最典型的 ANR)
Input ANR,大部分用户直接感知到的 ANR 就是它。你点了一下屏幕,5 秒内系统没把事件分发下去,或者分发后主线程没处理完 — 直接弹窗。
系统是怎么判断的?我简单解释下。InputDispatcher 会向主线程发送一个 KEY 或 MOTION 事件,同时开启一个 5 秒的定时器。如果超时前主线程没有处理完毕,或者没有返回 “处理完成” 的信号,ANR 就触发了。
典型触发场景:
- 主线程在做网络请求或 IO 读写(比如读 SharedPreferences 写大文件)
- 主线程在进行复杂的布局解析(嵌套太多
RelativeLayout或ConstraintLayout过深) - 主线程在
Touch事件回调里执行了耗时循环或同步锁等待
复现策略:
这个最容易复现。我在项目里遇到过,直接在 Button 的 onClick 里加个 Thread.sleep(6000) 就行。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 模拟主线程耗时 6 秒
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
SystemClock.sleep() 更贴近真实场景,因为它不会抛出 InterruptedException,逻辑更干净。
2. BroadcastReceiver 超时(前台的 10 秒,后台的 60 秒)
Broadcast ANR 在项目中也很常见。系统在发送广播后开始计时:前台广播 10 秒,后台广播 60 秒。如果 onReceive() 方法在这些时间内没返回,就 ANR 了。
这里有个重点:onReceive() 默认在主线程执行。你一启动广播就直接做网络请求或者写数据库,超过 10 秒 — ANR 没跑了。
典型触发场景:
- 在
onReceive()里直接调用了HttpURLConnection同步请求 - 接收系统广播(如
ACTION_BATTERY_CHANGED)时做了大量数据处理 - 多个动态注册的广播接收器执行时间长或相互等待锁
复现策略:
我常常用一个 BroadcastReceiver 来演示 onReceive() 里做耗时操作:
// 注册一个本地广播
IntentFilter filter = new IntentFilter("com.example.ACTION_TEST_ANR");
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 模拟在广播里做网络请求(同步)
try {
URL url = new URL("https://www.baidu.com/sleep");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);
conn.connect();
// 这里超过 10 秒必 ANR
} catch (Exception e) {
e.printStackTrace();
}
}
}, filter);
// 发送广播
sendBroadcast(new Intent("com.example.ACTION_TEST_ANR"));
AndroidManifest.xml 静态注册的广播里做过数据库操作,一分钟内没完成还好,一完成直接 ANR。后来全改 IntentService 或者在工作线程执行了。
3. Service 超时(20 秒的执行限制)
Service 的 ANR 相对隐蔽一些。因为很多开发者以为 Service 就是后台跑的,怎么慢都没事。其实不然。服务启动时,系统会给 onStartCommand() 或 onBind() 方法一个 20 秒 的计时。如果超时没返回,还是 ANR。
典型触发场景:
- 在
onStartCommand()里直接执行大文件下载 - 在
onStartCommand()里调用Thread.sleep()超过 20 秒 - 在
onCreate()里初始化重量级资源(数据库连接、大量图片内存缓存)超过 20 秒
复现策略:
直接在 onStartCommand 里加一个 25 秒的 sleep,保证 20 秒没返回 — ANR 就来了:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 模拟 25 秒耗时工作
try {
Thread.sleep(25000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return START_STICKY;
}
JobIntentService 或 WorkManager,而不是裸 Service。这样可以避免 ANR,同时还能利用系统调度省电。
4. ContentProvider 超时(10 秒的查询限制)
Provider 的 ANR 比较少见,但一旦触发往往很隐蔽。系统会给每个 ContentProvider 的 query()、insert()、update()、delete() 方法设置 10 秒 的超时。如果 Provider 操作没按时完成,调用方进程和 Provider 进程都会一起 ANR。
典型触发场景:
- 在
query()里执行复杂的Cursor遍历或临时网络请求 - 在
onCreate()里打开超大数据库文件或下载远程字典 - 跨进程调用 Provider 时,数据量大导致序列化/反序列化时间过长
复现策略:
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 模拟耗时查询 12 秒
SystemClock.sleep(12000);
return super.query(uri, projection, selection, selectionArgs, sortOrder);
}
五种类型的关键对比
| ANR 类型 | 超时时间 | 触发函数 | 常见复现场景 | 关键日志关键字 |
|---|---|---|---|---|
| Input | 5s | 事件分发(如 onTouchEvent) | 主线程耗时操作 | InputDispatcher |
| Broadcast | 前台 10s / 后台 60s | onReceive() | 在 onReceive 里做网络请求 | BroadcastQueue |
| Service | 20s | onStartCommand / onBind | 主线程执行大文件下载 | ActiveServices |
| Provider | 10s | query / insert / update / delete | 跨进程大数据查询 | ContentProvider |
表格记不住很正常。你只要记住一个原则:主线程不能做任何耗时操作。不管是哪种类型的 ANR,归根结底都是因为在主线程上干了不该干的事。
为什么系统要设这些超时?
说白了,用户交互不能被阻塞。用户不会关心你的广播在做什么,他只关心为什么点不动屏幕。所以每个组件都有严格的“时间预算”。
ANR 类型全景图
其实只要理解了系统为每种组件设置的“时间锁”,排查起来就清晰多了。说白了,你只需要判断:是哪一种组件超时了? 然后去看对应的日志节点就行。下一波遇到ANR时,你打开 traces.txt 先搜这五个关键字之一:InputDispatcher、BroadcastQueue、ActiveServices、ContentProvider。对症下药,事半功倍。