ANR 的四种类型:Input、Broadcast、Service、Provider

这一章我们聊聊 ANR 的具体分类。说白了,系统给每个组件都设了个“超时闹钟”。不同类型的 ANR,触发场景、超时时间、排查思路都不一样。

我个人习惯把 ANR 类型当作“倒计时”来看。主线程没在规定时间内干完活,系统就认为它 “Not Responding” 了。一共就四类:Input 事件、BroadcastReceiver、Service、ContentProvider。

核心要点: 理解每种 ANR 的超时机制,就是抓住了问题的本质。

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();
        }
    }
});
小技巧: 实际调试时,别用 sleep,用 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);
}
注意: ContentProvider 的 ANR 不会弹 “无响应” 对话框,而是直接导致调用线程被 Block 住。如果调用方是主线程,那就直接卡死 — 表现形式又回到 Input ANR。我曾经在 ContentProvider 里做数据库升级迁移时踩过这个坑,排查了很久才发现是 Provider 超时。

五种类型的关键对比

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 类型全景图 Android 主线程(UI线程) InputDispatch 超时 5 秒 BroadcastReceiver 超时 10s / 60s Service 超时 20 秒 ContentProvider 超时 10 秒 关键点 1. Input ANR 用户直接感知(弹框) 2. Broadcast 前台 10s / 后台 60s 超时 3. Service 和 Provider 只能靠 log 分析,不弹框 4. 所有类型归根结底都是主线程阻塞

其实只要理解了系统为每种组件设置的“时间锁”,排查起来就清晰多了。说白了,你只需要判断:是哪一种组件超时了? 然后去看对应的日志节点就行。下一波遇到ANR时,你打开 traces.txt 先搜这五个关键字之一:InputDispatcher、BroadcastQueue、ActiveServices、ContentProvider。对症下药,事半功倍。