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时,就踩过这个坑——开机广播收不到了,排查了半天才发现是系统改了策略。
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之前是可行的,后来系统权限收紧,就没那么容易了。
11.3 粘性广播
粘性广播(Sticky Broadcast)是一个比较特殊的存在。它发送后,即使没有接收器在监听,广播也会「粘」在系统里。等后面有接收器注册了,系统会立即把这条广播发给它。
// 发送粘性广播(需要权限)
sendStickyBroadcast(intent);
// 接收器注册后,会立即收到之前粘住的广播
registerReceiver(receiver, filter);
说实话,粘性广播在Android 5.0以后已经被标记为废弃(deprecated)了。为什么?因为它会导致系统内存泄漏,而且安全性很差。任何应用都能读取粘性广播中的数据。
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图来展示广播从发送到接收的完整流程,包括有序和无序两种路径:
11.6 总结
广播机制看似简单,但坑不少。我总结几个关键点:
- 注册方式:静态注册适合系统广播(如开机广播),动态注册适合应用内通信。Android 8.0+对静态注册限制很多,注意适配。
- 发送方式:无序广播效率高,有序广播可以控制优先级和截断。根据场景选择。
- 粘性广播:已废弃,别用了。用LocalBroadcastManager或EventBus替代。
- ANR问题:onReceive()中不要做耗时操作,10秒超时很容易触发ANR。轻量操作直接处理,重量操作启动Service。
嗯,广播机制就讲到这里。记住一句话:广播是轻量级的通信方式,别把它当线程池用。