17、BroadcastReceiver:静态注册与动态注册、系统广播监听、自定义广播发送

广播,说白了就是 Android 系统里的一种「喊话」机制。你喊一嗓子,谁想听谁听。我刚开始接触这个组件时,觉得它挺简单的——不就是发个消息、收个消息嘛。但实际项目里踩过几次坑之后,才发现这里面的门道还真不少。

今天咱们就把 BroadcastReceiver 彻底聊透。从注册方式到系统广播监听,再到自定义广播的发送,我会把我在实战中积累的经验和教训都抖出来。

广播的两种注册方式

Android 里的广播接收器有两种注册方式:静态注册动态注册。这两种方式各有各的适用场景,选错了可是会出问题的。

静态注册

静态注册是在 AndroidManifest.xml 里声明 Receiver。应用安装时,系统就会解析这个声明。即使你的应用没在运行,广播来了也能唤醒它。

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

对应的 Receiver 类:

public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if ("android.intent.action.BOOT_COMPLETED".equals(action)) {
            // 开机完成,启动你的服务或提醒
        }
    }
}
注意:从 Android 8.0(API 26)开始,系统对静态注册的限制越来越严。大部分隐式广播已经不允许静态注册了。我去年做一个后台保活项目时就吃了这个亏——静态注册的广播死活收不到,查了半天文档才发现是系统限制。

动态注册

动态注册是在代码里调用 registerReceiver() 方法。这种方式更灵活,但有个前提——你的应用必须在运行中,而且注册的组件必须处于活跃状态。

// 在 Activity 或 Service 中注册
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.BATTERY_CHANGED");
filter.addAction("android.intent.action.TIME_TICK");
registerReceiver(batteryReceiver, filter);

// 别忘了在 onDestroy 或 onPause 中取消注册
@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(batteryReceiver);
}
我的习惯:动态注册的 Receiver 一定要在合适的生命周期节点取消注册。我一般会在 onResume 里注册,onPause 里取消。这样能避免内存泄漏,也能保证界面在前台时能收到广播。

系统广播监听实战

系统广播有很多种,常用的包括:开机完成、网络状态变化、电量变化、屏幕亮灭等。咱们挑几个典型的说说。

广播 Action 触发时机 注册方式建议
BOOT_COMPLETED 系统启动完成 静态注册
CONNECTIVITY_CHANGE 网络连接变化 动态注册
BATTERY_CHANGED 电量或充电状态变化 动态注册
SCREEN_ON / SCREEN_OFF 屏幕亮起/熄灭 动态注册
TIME_TICK 每分钟变化 动态注册

监听网络状态变化是个很常见的需求。我做过一个视频播放器,需要根据网络类型切换清晰度。代码大概是这样的:

private BroadcastReceiver networkReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager cm = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (activeNetwork != null && activeNetwork.isConnected()) {
            int type = activeNetwork.getType();
            if (type == ConnectivityManager.TYPE_WIFI) {
                // WiFi 环境,可以加载高清
            } else {
                // 移动网络,降低清晰度
            }
        } else {
            // 网络断开,暂停播放
        }
    }
};
避坑指南:我曾经在监听 CONNECTIVITY_CHANGE 时,直接在 onReceive 里弹 Toast。结果网络频繁切换时,Toast 一个接一个弹出来,用户体验极差。后来我加了个防抖逻辑——1秒内只响应一次。

自定义广播发送

除了监听系统广播,我们也可以自己发广播。这在组件间通信、跨进程通信时特别有用。

发送普通广播

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

发送有序广播

有序广播可以按优先级依次传递,而且每个接收者可以截断广播或修改数据。

Intent intent = new Intent("com.example.ORDERED_ACTION");
sendOrderedBroadcast(intent, null);

接收者可以在 onReceive 中调用 abortBroadcast() 来截断广播,或者用 setResultExtras() 传递数据给下一个接收者。

小技巧:如果你想让某个接收者优先处理广播,可以在 AndroidManifest 中设置 priority 属性,数值越大优先级越高。我一般在做消息推送时,会让核心模块的优先级设高一些,确保它能先处理。

知识体系总览

下面这张图把 BroadcastReceiver 的核心知识点串起来了。你可以把它当作一个快速参考。

BroadcastReceiver 知识体系 BroadcastReceiver 静态注册 动态注册 AndroidManifest 声明 可被系统唤醒 代码中 registerReceiver 需手动 unregister 广播类型 普通广播 有序广播 粘性广播(已废弃) 常见系统广播 BOOT_COMPLETED CONNECTIVITY_CHANGE BATTERY_CHANGED

一些实战建议

说了这么多,最后再分享几点我个人的经验:

  • 别在 onReceive 里做耗时操作——这个方法的执行时间只有 10 秒左右,超了会 ANR。我见过有人直接在广播里做网络请求,结果应用卡死。
  • LocalBroadcastManager 了解一下——如果你的广播只在应用内部传递,用 LocalBroadcastManager 更安全、更高效。它不会跨进程,也不会有安全风险。
  • Android 8.0 之后的限制要牢记——大部分隐式广播已经不能静态注册了。如果你的目标版本比较高,建议优先考虑动态注册或者 JobScheduler。
  • 有序广播的优先级别乱设——我曾经把某个接收者的优先级设得特别高,结果它截断了广播,后面的接收者全收不到。调试了半天才发现问题。

广播这个机制,说简单也简单,说复杂也复杂。关键是要理解它的设计初衷——轻量、异步、解耦。用好了,它能让你的应用架构更清晰;用不好,反而会引入一堆奇怪的问题。

嗯,今天就聊到这儿。希望这些内容对你有帮助。


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