12、BroadcastReceiver机制:静态与动态注册、有序广播与粘性广播、广播超时处理
BroadcastReceiver,说白了就是Android系统里的「消息喇叭」。
组件之间想传递消息,又不想耦合太紧,怎么办?广播呗。我当年刚接触Android时,觉得这玩意儿不就是个观察者模式嘛。后来踩了几个坑才发现——嗯,没那么简单。
12.1 静态注册 vs 动态注册
先说说注册方式。这是最基础,也是最容易出问题的地方。
12.1.1 静态注册
静态注册,就是在AndroidManifest.xml里声明Receiver。系统安装APK时就会解析,应用不用启动也能收到广播。
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
我在项目中遇到过一个问题:静态注册的Receiver,如果应用被用户「强行停止」,系统就不会再给它发广播了。这是Android 3.1之后引入的机制,很多人不知道。
12.1.2 动态注册
动态注册,就是在代码里调用registerReceiver()。生命周期跟着组件走,灵活性更高。
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.MY_ACTION");
MyReceiver receiver = new MyReceiver();
registerReceiver(receiver, filter);
// 别忘了在onDestroy里解注册
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
我曾经见过一个同事,Activity里动态注册了Receiver,但onDestroy里忘了unregister。结果Activity销毁后,Receiver还活着,一收到广播就崩溃——因为持有的Context已经失效了。这个坑,踩一次就记住了。
| 对比项 | 静态注册 | 动态注册 |
|---|---|---|
| 注册时机 | 安装时解析 | 运行时调用 |
| 生命周期 | 独立于组件 | 跟随组件 |
| 接收系统广播 | 支持(部分受限) | 完全支持 |
| 应用未启动 | 可接收 | 不可接收 |
| 内存泄漏风险 | 低 | 高(忘记unregister) |
12.2 有序广播与粘性广播
广播按发送方式,可以分为三类:普通广播、有序广播、粘性广播。普通广播最简单,咱们重点说后两个。
12.2.1 有序广播
有序广播,就是广播按优先级依次传递。每个Receiver可以决定是否继续传递,还能修改广播内容。
// 发送有序广播
Intent intent = new Intent("com.example.ORDERED_ACTION");
intent.putExtra("data", "原始数据");
sendOrderedBroadcast(intent, null);
// Receiver中截断广播
@Override
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("data");
// 可以修改数据
setResultData("修改后的数据");
// 也可以截断广播
abortBroadcast();
}
我建议你在做消息推送或事件分发时,优先考虑有序广播。为什么?因为你可以控制处理顺序。比如先让登录模块处理,再让UI模块刷新——顺序可控,逻辑清晰。
12.2.2 粘性广播
粘性广播,说白了就是「迟到的广播」。发送之后,即使Receiver还没注册,等注册时也能收到最后一条粘性广播。
// 发送粘性广播(需要权限)
sendStickyBroadcast(new Intent("com.example.STICKY_ACTION"));
// 注册时,会立即收到最后一条粘性广播
registerReceiver(receiver, new IntentFilter("com.example.STICKY_ACTION"));
嗯,这里要注意——粘性广播在Android 5.0之后已经被标记为deprecated了。为什么?因为它会暴露系统状态,有安全隐患。我个人建议:新项目就别用了,用LocalBroadcastManager或者LiveData替代吧。
12.3 广播超时处理
这是面试常问,也是实际开发中容易翻车的地方。
系统对广播接收是有时间限制的。前台广播超时是10秒,后台广播是60秒。如果onReceive()里做了耗时操作,超过了这个时间——系统就会认为Receiver卡死,直接杀掉进程。
我曾经接手过一个项目,里面有个Receiver在onReceive里直接做了网络请求。用户反馈说「收不到广播」,我一看日志——全是ANR。后来改成用IntentService处理,问题就解决了。
// 正确的做法:用IntentService处理耗时任务
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 只做轻量操作
Intent service = new Intent(context, MyService.class);
service.putExtras(intent);
context.startService(service);
}
}
你想想看,为什么系统要设超时?因为广播是系统级机制,如果一个Receiver卡住,整个系统的消息传递都会受影响。Android的设计哲学就是「不能让一个组件拖垮整个系统」。
12.4 知识体系总览
下面这张图,是我梳理的BroadcastReceiver核心知识结构。建议你保存下来,面试前看一眼。
12.5 避坑指南
最后,我总结几个实战中容易踩的坑:
- 不要在主线程做耗时操作——这个前面说了,但值得重复一遍。我曾经见过有人直接在onReceive里写数据库操作,结果广播一多,ANR频发。
- 动态注册一定要成对出现——register和unregister要成对。最好在onStart和onStop里做,而不是onCreate和onDestroy。因为onDestroy不一定执行。
- LocalBroadcastManager更安全——如果你的广播只在应用内部传递,用LocalBroadcastManager。它不走系统进程,效率更高,也不会被其他应用拦截。
- 有序广播的优先级别乱设——优先级太高,可能会影响系统组件的正常接收。我一般控制在100以内。
嗯,BroadcastReceiver的内容就这些。说白了,它就是个消息传递的桥梁,用好了事半功倍,用不好就是各种坑。记住一条核心原则:轻量、快速、及时解注册。做到这三点,基本不会出大问题。