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之后引入的机制,很多人不知道。

注意:从Android 8.0开始,大部分隐式广播已经不支持静态注册了。只有少数系统广播(如BOOT_COMPLETED、TIME_SET等)例外。如果你还在用静态注册监听网络变化——赶紧改吧,跑不起来的。

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模块刷新——顺序可控,逻辑清晰。

小技巧:有序广播的优先级在intent-filter的android:priority属性中设置,数值越大优先级越高。范围是-1000到1000。我一般习惯用100、200这样的值,留点余量给其他模块。

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卡死,直接杀掉进程。

核心要点:onReceive()运行在主线程,不能做耗时操作。这是铁律。

我曾经接手过一个项目,里面有个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核心知识结构。建议你保存下来,面试前看一眼。

BroadcastReceiver 注册方式 广播类型 静态注册 动态注册 普通广播 有序广播 粘性广播 超时处理 前台10秒 后台60秒 核心原则:onReceive() 不做耗时操作 耗时任务 → IntentService / JobScheduler Android 8.0+ 隐式广播静态注册受限

12.5 避坑指南

最后,我总结几个实战中容易踩的坑:

  • 不要在主线程做耗时操作——这个前面说了,但值得重复一遍。我曾经见过有人直接在onReceive里写数据库操作,结果广播一多,ANR频发。
  • 动态注册一定要成对出现——register和unregister要成对。最好在onStart和onStop里做,而不是onCreate和onDestroy。因为onDestroy不一定执行。
  • LocalBroadcastManager更安全——如果你的广播只在应用内部传递,用LocalBroadcastManager。它不走系统进程,效率更高,也不会被其他应用拦截。
  • 有序广播的优先级别乱设——优先级太高,可能会影响系统组件的正常接收。我一般控制在100以内。

嗯,BroadcastReceiver的内容就这些。说白了,它就是个消息传递的桥梁,用好了事半功倍,用不好就是各种坑。记住一条核心原则:轻量、快速、及时解注册。做到这三点,基本不会出大问题。

公众号:蓝海资料掘金营,微信deep3321