11. BroadcastReceiver与广播机制:广播注册(静态/动态)、广播发送流程(有序/无序)、粘性广播、广播超时与ANR

广播机制,说白了就是Android系统里的「消息喇叭」。

一个组件喊一嗓子,其他感兴趣的组件就能听到。我刚开始接触Framework时,觉得广播不就是个事件通知嘛,有啥难的?直到我在项目中遇到一个因为广播超时导致ANR的线上事故,才意识到这玩意儿的水深得很。

今天咱们就把广播机制的底裤扒干净。

11.1 广播注册:静态 vs 动态

广播接收器要能收到广播,必须先注册。注册方式有两种:静态注册和动态注册。这两种方式各有各的适用场景,我建议你根据实际需求来选择。

11.4.1 静态注册

静态注册是在AndroidManifest.xml中声明标签。这种方式的特点是:即使应用进程被杀死了,广播来了也能把进程拉起来

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

嗯,这里要注意:从Android 8.0(API 26)开始,系统对静态注册做了大量限制。大多数隐式广播(不是发给你的广播)已经无法通过静态注册接收了。我当年升级targetSdkVersion到26时,就踩过这个坑——开机广播收不到了,排查了半天才发现是系统改了策略。

避坑指南:我曾经在项目中把所有的广播都改成静态注册,结果发现很多广播根本收不到。后来才明白,Android 8.0以后,只有少数系统广播(如BOOT_COMPLETED、TIME_SET等)还支持静态注册。其他场景,老老实实用动态注册吧。

11.4.2 动态注册

动态注册是在代码中通过registerReceiver()方法注册。这种方式的特点是:生命周期跟随注册的组件,组件销毁了,广播接收器也就失效了。

// 注册
IntentFilter filter = new IntentFilter("com.example.MY_ACTION");
MyReceiver receiver = new MyReceiver();
registerReceiver(receiver, filter);

// 别忘了在onDestroy中注销
@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(receiver);
}

我个人习惯在onResume()中注册,在onPause()中注销。为什么?因为这样可以保证Activity可见时才接收广播,避免内存泄漏。你想想看,如果Activity已经不可见了,还占着广播接收器不放,那不就是浪费资源吗?

对比项 静态注册 动态注册
注册位置 AndroidManifest.xml 代码中registerReceiver()
生命周期 独立于组件,进程存活即可 跟随组件生命周期
进程拉起 支持(广播到达时拉起进程) 不支持
优先级 可在XML中设置 可在代码中设置
Android 8.0+限制 大部分隐式广播受限 基本不受限

11.2 广播发送流程:有序 vs 无序

广播发送分为两种:无序广播(Normal Broadcast)和有序广播(Ordered Broadcast)。它们的区别,说白了就是一个「一窝蜂」、一个「排排队」。

11.2.1 无序广播

无序广播通过sendBroadcast()发送。所有注册了对应Action的接收器,几乎同时收到广播,没有先后顺序。

Intent intent = new Intent("com.example.MY_ACTION");
intent.putExtra("key", "value");
sendBroadcast(intent);

无序广播的特点是:异步、高效、不可截断。接收器之间互不影响,一个接收器处理慢了,不会影响其他接收器。但缺点也很明显——你没法控制谁先收到。

11.2.2 有序广播

有序广播通过sendOrderedBroadcast()发送。接收器按照优先级依次接收广播,优先级高的先收到。而且,前面的接收器可以截断广播,后面的就收不到了。

sendOrderedBroadcast(intent, null);

在接收器中,可以通过abortBroadcast()截断广播:

@Override
public void onReceive(Context context, Intent intent) {
    // 处理广播
    // 如果不想让后面的接收器收到,就调用:
    abortBroadcast();
}

我记得有一次做短信拦截功能,就是用有序广播实现的。优先级设得高高的,先收到短信广播,判断是垃圾短信就直接截断,后面的接收器就收不到了。嗯,这个方案在Android 4.4之前是可行的,后来系统权限收紧,就没那么容易了。

核心区别:无序广播是「并发」的,有序广播是「串行」的。有序广播中,每个接收器有10秒的超时时间(Android 4.0+),超时会导致ANR。

11.3 粘性广播

粘性广播(Sticky Broadcast)是一个比较特殊的存在。它发送后,即使没有接收器在监听,广播也会「粘」在系统里。等后面有接收器注册了,系统会立即把这条广播发给它。

// 发送粘性广播(需要权限)
sendStickyBroadcast(intent);

// 接收器注册后,会立即收到之前粘住的广播
registerReceiver(receiver, filter);

说实话,粘性广播在Android 5.0以后已经被标记为废弃(deprecated)了。为什么?因为它会导致系统内存泄漏,而且安全性很差。任何应用都能读取粘性广播中的数据。

避坑指南:我曾经在一个遗留项目中看到大量使用粘性广播的代码,结果导致应用频繁OOM。后来全部改成了用LocalBroadcastManager或者EventBus。如果你还在用粘性广播,我建议你尽快迁移。

11.4 广播超时与ANR

这是广播机制中最容易踩坑的地方。广播接收器的onReceive()方法运行在主线程,如果处理时间过长,就会导致ANR。

具体来说:

  • 无序广播:每个接收器的onReceive()有10秒的超时时间(Android 4.0+)
  • 有序广播:每个接收器同样有10秒的超时时间
  • 前台广播:通过FLAG_RECEIVER_FOREGROUND标记的广播,超时时间缩短到5秒

为什么会超时?说白了就是你在onReceive()里做了耗时操作,比如网络请求、数据库写入、文件IO等。这些操作都不应该放在广播接收器里。

我的建议:onReceive()中只做轻量级操作,比如启动一个Service、发送一个通知、更新一下UI。如果需要做耗时操作,请使用IntentService或者JobScheduler
// 错误示范:在onReceive中做耗时操作
@Override
public void onReceive(Context context, Intent intent) {
    // 这里做网络请求,大概率会ANR
    String result = doNetworkRequest(); 
}

// 正确做法:启动Service来处理
@Override
public void onReceive(Context context, Intent intent) {
    Intent serviceIntent = new Intent(context, MyService.class);
    context.startService(serviceIntent);
}

我记得有一次线上事故,用户反馈App经常卡死。排查后发现是一个广播接收器里做了数据库批量插入操作,数据量大的时候直接超时ANR。后来改成在onReceive()中只发一个EventBus事件,由后台线程去处理数据库操作,问题就解决了。

11.5 广播机制核心流程图

下面我用一张SVG图来展示广播从发送到接收的完整流程,包括有序和无序两种路径:

广播发送与接收流程图 广播发送者 sendBroadcast() 无序 sendOrderedBroadcast() 有序 AMS查找所有匹配的接收器 并发发送给所有接收器 每个接收器独立处理(10秒超时) AMS按优先级排序接收器 串行发送,优先级高的先收到 可调用abortBroadcast()截断 超时 → ANR

11.6 总结

广播机制看似简单,但坑不少。我总结几个关键点:

  • 注册方式:静态注册适合系统广播(如开机广播),动态注册适合应用内通信。Android 8.0+对静态注册限制很多,注意适配。
  • 发送方式:无序广播效率高,有序广播可以控制优先级和截断。根据场景选择。
  • 粘性广播:已废弃,别用了。用LocalBroadcastManager或EventBus替代。
  • ANR问题:onReceive()中不要做耗时操作,10秒超时很容易触发ANR。轻量操作直接处理,重量操作启动Service。

嗯,广播机制就讲到这里。记住一句话:广播是轻量级的通信方式,别把它当线程池用