BroadcastReceiver 超时:onReceive 执行限制与阈值

BroadcastReceiver 的 ANR,说实话,是我早期做 Android 开发时踩得比较多的坑之一。你想想看,一个广播发出去,onReceive 里做了个网络请求或者数据库写操作,结果十几秒没返回 —— 系统直接弹 ANR 对话框。用户看到那个对话框,基本就该卸载 App 了。

这一章我们专门聊聊 BroadcastReceiver 的超时机制。我会把我这些年踩过的坑、总结的经验都摊开来说。

BroadcastReceiver 超时知识体系 BroadcastReceiver 超时 onReceive 执行限制 异步广播 vs 同步广播 超时阈值 主线程执行 不能做耗时操作 goAsync 异步处理 同步广播:串行、阻塞 异步广播:并行、非阻塞 有序广播:按优先级传递 前台广播:10 秒 后台广播:60 秒 Android 15+ 机制变更 核心原则:onReceive 必须轻量,任何耗时操作都必须异步处理

onReceive 的执行限制 —— 为什么不能做耗时操作?

BroadcastReceiver 的 onReceive 方法运行在哪儿?主线程。这一点特别关键。我见过不少开发者在 onReceive 里直接做网络请求、写数据库、甚至执行大文件复制 —— 这其实是在玩火。

系统给 onReceive 设定的超时时间其实很短。前台广播只有 10 秒,后台广播稍微长点,60 秒。一旦超时,系统就会判定这个 Receiver 无响应,直接 ANR。

关键事实:onReceive 返回后,系统才会认为广播处理完毕。如果你在 onReceive 里启动了新线程做耗时操作,但 onReceive 本身很快返回了 —— 这样不会超时。但如果你把耗时操作直接写在 onReceive 的主路径上,那每一毫秒都在消耗超时倒计时。

我自己的习惯是:onReceive 里只做两件事 —— 1)解析 Intent 中的数据;2)通过 goAsync 或启动 Service/JobScheduler 把实际工作交出去。

goAsync —— 唯一的官方异步方案

其实 Android 官方提供了一个叫 goAsync() 的机制。这个东西有点冷门,但非常实用。不绕弯子,直接看代码:

public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 1. 调用 goAsync,获得 PendingResult
        final PendingResult pendingResult = goAsync();

        // 2. 在后台线程做实际工作
        new Thread(() -> {
            try {
                // 模拟耗时操作
                Thread.sleep(5000);
                // 处理逻辑...
            } finally {
                // 3. 必须调用 finish() 通知系统
                pendingResult.finish();
            }
        }).start();

        // onReceive 立即返回,不阻塞主线程
    }
}

这里有个陷阱我必须要提醒你:goAsync() 虽然能把工作挪到后台线程,但系统给这个异步任务的超时时间 并没有延长。前台广播还是 10 秒,后台广播还是 60 秒。如果你在后台线程里做了 20 秒的操作,而这是一个前台广播 —— 照 ANR 不误。

我曾经踩过这个坑:有个版本上线后,用户反馈偶尔出现 ANR。我查了很久才发现,同事在 goAsync 的线程里做了一次网络请求,网络不稳定时耗时超过 10 秒。前台广播 + 10 秒阈值,直接命中。后来我们改成了启动一个 Foreground Service 来处理这个请求,问题才彻底解决。

异步广播 vs 同步广播 —— 本质区别在哪?

很多新人搞不清这两种广播的区别。我这里用一句话说清楚:

  • 同步广播(普通广播):多个 Receiver 按顺序执行,前一个 Receiver 的 onReceive 返回后,下一个才执行。阻塞的,串行的。
  • 异步广播(sendBroadcast):系统把广播发给所有匹配的 Receiver,每个 Receiver 在自己的主线程上独立处理,互不等待。非阻塞的,并行的。

但注意,这里说的「异步」只是 Receiver 之间的相互不阻塞,并不代表 onReceive 本身是异步的。每个 Receiver 的 onReceive 仍然在自己的主线程上跑,仍然受超时限制。

还有一种特殊的 有序广播(ordered broadcast),它也是同步的,但 Receiver 之间可以按优先级传递结果,并且可以截断广播。这个机制在系统广播里用得比较多,比如短信拦截。

广播类型 Receiver 执行方式 是否阻塞 典型场景
普通广播(同步) 串行,按顺序 是 系统通知、应用内事件
异步广播 并行,各自独立 否 不需要等待结果的场景
有序广播 串行,按优先级 是 短信拦截、电话拦截

超时阈值 —— 前台 10 秒,后台 60 秒,但别卡着底线

不同 Android 版本下,超时阈值其实有过微调。但当前主流版本(Android 12 ~ 15)的通用规则是:

  • 前台广播(Foreground broadcast):超时时间 10 秒
  • 后台广播(Background broadcast):超时时间 60 秒

这里说的「前台」和「后台」,指的是发送广播时 App 是否处于可见状态。如果 App 在后台,系统会给更长的超时时间,但别因此放松警惕 —— 60 秒干不完的活多了去了。

Android 15 开始,系统对广播超时做了更严格的限制。后台广播的超时时间从 60 秒缩短到 30 秒,而且系统会检测 Receiver 是否调用了 goAsync 但没有及时 finish。如果你的 App 要适配 Android 15 以上,这部分一定要仔细检查。

我个人的建议:不要把超时时间当作「安全区」。前台广播按 5 秒以内规划,后台广播按 15 秒以内规划。留出余量,运维不慌。你想想看,用户手机可能正在卡顿、CPU 被其他 App 占满、IO 在排队 —— 你的 10 秒操作实际可能跑成 20 秒。

避坑指南 —— 我踩过的几个真实案例

案例 1:曾经有一个版本,我们在 onReceive 里直接调了一个 SDK 的同步初始化方法。那个方法内部做了网络请求 + 插件加载,耗时不稳定。线上 ANR 率飙升到 0.8%。排查下来,广播超时占了 70%。解决方案是把初始化改成懒加载,onReceive 只发一个 EventBus 事件,让业务层自己去初始化。

案例 2:另一个项目里,同事用了 goAsync 但忘了调用 finish()。结果系统一直等这个 Receiver 结束,直到超时。这种 bug 特别隐蔽,因为不是每次都触发,只在系统负载高的时候才出现。我们后来加了一套检查机制 —— 在 Receiver 里用 Handler 做兜底,超过 8 秒还没 finish 就自动打 log 并强制 finish。

案例 3:有序广播的截断问题。我们在系统通话状态广播里做逻辑处理,结果另一个 App 的高优先级 Receiver 截断了广播,我们这边根本收不到。解决方案是改用动态注册 + 设置最高优先级,同时加了一个兜底的轮询机制。

总结一下重点

  • onReceive 运行在主线程,不能做耗时操作 —— 这是铁律
  • goAsync 可以把工作挪到后台线程,但超时时间不变
  • 同步广播串行阻塞,异步广播并行不阻塞,有序广播按优先级传递
  • 前台广播超时 10 秒,后台广播超时 60 秒(Android 15 后台改为 30 秒)
  • 每个 Receiver 必须调用 finish(),否则系统会一直等待

嗯,BroadcastReceiver 的超时问题其实就这些核心要点。但说实话,越简单的东西越容易出问题 —— 因为大家都不重视。我建议你把项目里所有 Receiver 都过一遍,看看 onReceive 里有没有直接做耗时操作的,有的话赶紧改。