24、BroadcastReceiver:静态注册与动态注册、有序广播与无序广播、Android 8.0+隐式广播限制
BroadcastReceiver,说白了就是Android系统里的「消息喇叭」。某个事件发生了,系统吼一嗓子,所有感兴趣的App都能听到。我刚开始做Android开发时,觉得这玩意儿挺简单,不就是收个广播嘛。后来踩了几个坑才发现,这里面的门道还真不少。
今天咱们就把广播接收器的几个核心知识点掰扯清楚:静态注册和动态注册到底怎么选?有序广播和无序广播有啥区别?Android 8.0之后为什么好多广播收不到了?
静态注册 vs 动态注册
这两种注册方式,是我在项目中经常需要权衡的。先看个对比表格,心里有个底:
| 对比项 | 静态注册 | 动态注册 |
|---|---|---|
| 注册方式 | 在AndroidManifest.xml中声明 | 在代码中调用registerReceiver() |
| 生命周期 | 随App安装而存在,即使App未运行 | 随组件的生命周期,需手动解注册 |
| 接收时机 | App未启动也能接收(部分受限) | 组件处于活跃状态时才能接收 |
| 典型场景 | 开机广播、安装/卸载广播 | 网络变化、屏幕亮灭等实时性要求高的场景 |
静态注册,就是在AndroidManifest.xml里写个<receiver>标签。像这样:
<receiver android:name=".MyBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
这样做的好处是,App没启动也能收到广播。我记得有个项目需要监听设备开机,然后启动一个后台服务。用静态注册就特别合适,用户一开机,广播就来了。
但要注意,Android 8.0之后,静态注册能收的广播越来越少了。这个后面细说。
动态注册,是在代码里写的:
// 注册
IntentFilter filter = new IntentFilter("com.example.MY_CUSTOM_ACTION");
MyReceiver receiver = new MyReceiver();
registerReceiver(receiver, filter);
// 别忘了解注册
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
我个人习惯用动态注册。为什么呢?因为它的生命周期可控。你在Activity的onResume()里注册,onPause()里解注册,就不会出现「Activity已经销毁了,广播还在跑」的尴尬情况。
有序广播 vs 无序广播
这个区别,用一个生活场景就能说清楚。
无序广播,就像在食堂里喊了一声「开饭了」。所有人同时听到,同时响应,谁先谁后无所谓。用sendBroadcast()发送。
有序广播,就像传话游戏。第一个人传给第二个人,第二个人传给第三个人,有先后顺序。而且每个人都可以截断消息,不让后面的人知道。用sendOrderedBroadcast()发送。
看代码:
// 发送无序广播
Intent intent = new Intent("com.example.MY_ACTION");
sendBroadcast(intent);
// 发送有序广播
Intent intent = new Intent("com.example.MY_ORDERED_ACTION");
sendOrderedBroadcast(intent, null);
有序广播的接收器可以设置优先级:
<receiver android:name=".HighPriorityReceiver">
<intent-filter android:priority="100">
<action android:name="com.example.MY_ORDERED_ACTION" />
</intent-filter>
</receiver>
优先级高的先收到。在接收器里,可以调用abortBroadcast()来截断广播:
@Override
public void onReceive(Context context, Intent intent) {
// 处理完自己的逻辑后,截断广播
abortBroadcast();
}
我在项目中用过有序广播做「消息拦截」。比如一个支付结果广播,先让安全模块检查,没问题再让UI模块更新界面。安全模块如果发现异常,直接abortBroadcast(),UI模块就收不到了。这种设计,说白了就是责任链模式。
Android 8.0+ 隐式广播限制
嗯,这里要注意。Android 8.0(API 26)之后,Google对广播做了大手术。大部分隐式广播,静态注册已经收不到了。
什么叫隐式广播?就是只通过action字符串匹配,不指定具体包名的广播。比如:
// 隐式广播 —— 只指定action
Intent intent = new Intent("com.example.SOME_ACTION");
sendBroadcast(intent);
而显式广播是指定了目标包名的:
// 显式广播 —— 指定了包名
Intent intent = new Intent("com.example.SOME_ACTION");
intent.setPackage("com.example.targetapp");
sendBroadcast(intent);
Android 8.0的限制是:静态注册的BroadcastReceiver,无法接收隐式广播。但动态注册不受影响。
为什么会这样?说白了,Google是为了省电和提升性能。你想啊,每个App都静态注册一堆广播,系统每次发广播都要唤醒一堆App,电量就这么耗没了。
那哪些广播还能用静态注册呢?Google留了一些白名单,比如:
BOOT_COMPLETED—— 开机广播LOCKED_BOOT_COMPLETED—— 锁屏状态下的开机完成QUICKBOOT_POWERON—— 快速开机- 一些系统级的、与安全相关的广播
完整的白名单,可以去官方文档查。我建议你直接记住一句话:能用动态注册就别用静态注册。除非你真的需要App没启动就收广播,而且那个广播在白名单里。
核心结论:
- 动态注册是主流,生命周期可控,不受8.0限制
- 静态注册只用于特定场景(开机、安装等),且受白名单限制
- 有序广播适合需要优先级和拦截的场景
- 无序广播适合一对多的通知场景
最后,画个图总结一下今天的内容:
好了,关于BroadcastReceiver的核心知识点就这些。记住一句话:能用动态就别用静态,能显式就别隐式。这样你的App在Android 8.0以上的设备上,就不会莫名其妙收不到广播了。
公众号:蓝海资料掘金营,微信deep3321