Android?广播!!!

作为安卓四大组件之一,广播(BroadcastReceiver)是安卓系统中实现跨组件、跨应用异步通信的核心机制,类似现实中的"电台广播"------发送者发出信号,所有匹配频率的接收者均可接收并响应。无论是监听系统状态变化(如开机、网络切换),还是实现应用内组件交互(如登录成功通知、下载完成提醒),广播都扮演着"万能信使"的角色。

但很多开发者刚接触广播时,总会遇到"静态广播接收不到""Android 8.0后广播失效""内存泄漏"等问题,其实核心原因是没吃透广播的本质、分类和系统限制。今天这篇博客,就从基础到进阶,把安卓广播讲透,新手能直接上手,进阶开发者能夯实基础、避开坑点。

一、广播的核心本质:什么是安卓广播?

安卓广播本质是一种基于"发布-订阅"模式的消息传递机制,用于在系统、应用之间传递事件通知,核心特点是"发送方不关心接收方是否存在、如何处理消息",只负责发送事件,接收方通过注册监听特定事件,收到消息后执行对应逻辑。

简单来说,广播机制包含三个核心要素,缺一不可:

  1. 广播发送者(Sender):可以是安卓系统(如系统开机、电量变化时自动发送广播)、应用内组件(Activity、Service等),也可以是其他第三方应用,通过发送Intent携带广播消息。

  2. 广播接收器(BroadcastReceiver):接收广播的核心组件,需继承BroadcastReceiver类,重写onReceive()方法,在该方法中处理广播消息(如刷新UI、启动服务)。

  3. 意图(Intent):广播的"载体",用于封装广播的动作(Action)、数据(Extra),接收者通过匹配Intent的Action来筛选自己需要接收的广播,类似"电台频率"的匹配。

需要注意的是,系统会优化广播的传递效率以保证系统性能,因此广播的送达时间无法保证;如果需要低延迟的跨进程通信,更推荐使用绑定服务(Bound Service)而非广播。

二、广播的分类:按3个维度划分,避免用错场景

安卓广播的分类有多种维度,不同类型的广播适用场景不同,误用会导致功能异常或性能问题,下面按"注册方式""发送方式""传播范围"三个核心维度,结合实战场景详细说明。

2.1 按注册方式划分:静态注册 vs 动态注册

注册广播是接收广播的前提,两种注册方式的核心区别的是"是否依赖应用进程",适用场景完全不同。

(1)静态注册(Manifest注册)

在AndroidManifest.xml文件中声明广播接收器,属于"常驻型"注册------无论应用是否处于运行状态,只要有匹配的广播发送,接收器就能接收(系统会自动启动应用进程)。

核心用法(以监听开机广播为例):

XML 复制代码
<!-- 1. 声明广播接收器 -->
<receiver
    android:name=".receiver.BootReceiver"
    android:exported="true">
    <!-- 2. 设置Intent过滤器,匹配开机广播的Action -->
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- 3. 申请开机广播权限(系统广播需对应权限) -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

对应的广播接收器代码:

XML 复制代码
public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 接收开机广播,执行逻辑(如启动后台服务)
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            Intent serviceIntent = new Intent(context, MyService.class);
            ContextCompat.startForegroundService(context, serviceIntent);
        }
    }
}

特点:常驻监听,不依赖应用进程;但自Android 8.0(API 26)起,系统对静态注册施加了严格限制------大多数隐式广播(未明确指定接收方包名的广播)无法通过静态注册接收,仅少数系统广播(如开机广播)例外。

(2)动态注册(代码注册)

在代码中(如Activity、Service中)通过registerReceiver()方法注册,属于"临时型"注册------依赖应用进程,当应用进程销毁(如Activity退出),需手动调用unregisterReceiver()注销,否则会导致内存泄漏。

核心用法(以监听网络变化为例):

java 复制代码
public class MainActivity extends AppCompatActivity {
    // 1. 声明广播接收器和过滤器
    private NetworkReceiver networkReceiver;
    private IntentFilter intentFilter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 2. 初始化接收器和过滤器(匹配网络变化Action)
        networkReceiver = new NetworkReceiver();
        intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    }

    // 3. 页面可见时注册广播
    @Override
    protected void onStart() {
        super.onStart();
        registerReceiver(networkReceiver, intentFilter);
    }

    // 4. 页面不可见时注销广播(关键:避免内存泄漏)
    @Override
    protected void onStop() {
        super.onStop();
        unregisterReceiver(networkReceiver);
    }

    // 自定义广播接收器
    class NetworkReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 处理网络变化逻辑
            ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo info = cm.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

特点:灵活可控,可根据业务需求动态注册/注销;不受Android 8.0以上隐式广播限制,是目前开发中最常用的注册方式;但必须记得注销,否则会导致内存泄漏。

2.2 按发送方式划分:普通广播 vs 有序广播

发送广播的方式决定了接收者的接收顺序和交互能力,两种方式的核心差异在于"是否可中断、可修改"。

(1)普通广播(Normal Broadcast)

最常用的广播类型,发送后所有匹配的接收者同时接收(无顺序),且接收者之间无法交互------不能中断广播传递,也不能修改广播内容。

发送方式:调用sendBroadcast()方法

XML 复制代码
// 1. 创建Intent,指定广播Action
Intent intent = new Intent();
intent.setAction("com.example.MY_BROADCAST");
// 2. 携带数据(可选)
intent.putExtra("msg", "这是普通广播");
// 3. 发送普通广播
sendBroadcast(intent);

适用场景:无需接收者交互的场景,如下载完成通知、登录状态同步等。

(2)有序广播(Ordered Broadcast)

广播发送后,接收者按"优先级"顺序依次接收(优先级高的先接收),且优先级高的接收者可以"中断广播"(让后续接收者无法接收),也可以"修改广播内容"(后续接收者收到的是修改后的数据)。

核心要点:

  • 优先级通过intentFilter.setPriority(int priority)设置,取值范围-1000~1000,值越大优先级越高;

  • 发送方式:调用sendOrderedBroadcast()方法;

  • 中断广播:调用abortBroadcast()方法;

  • 修改广播内容:通过setResultExtras()方法设置额外数据,后续接收者通过getResultExtras()获取。

适用场景:需要接收者按顺序处理、且可能需要中断/修改广播的场景,如消息审核、权限验证等。

2.3 按传播范围划分:全局广播 vs 本地广播

传播范围决定了广播是否能跨应用传递,主要解决"安全性"和"效率性"问题。

(1)全局广播(Global Broadcast)

默认的广播类型,广播可以跨应用传递------发送方发出的广播,其他应用只要注册了匹配的接收器,就能接收。

注意事项:存在安全隐患,可能出现"恶意应用伪造广播""广播内容泄露"等问题,如其他应用伪造你的应用广播,导致业务逻辑异常。

(2)本地广播(Local Broadcast)

仅在当前应用内传播的广播,发送方和接收方必须属于同一个应用,无法跨应用传递,从根源上解决了全局广播的安全问题,且效率更高(无需系统跨应用路由)。

核心用法:使用LocalBroadcastManager管理广播的注册、注销和发送,用法与动态注册类似,但仅作用于应用内。

java 复制代码
// 1. 获取LocalBroadcastManager实例
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);

// 2. 初始化接收器和过滤器
LocalReceiver localReceiver = new LocalReceiver();
IntentFilter filter = new IntentFilter("com.example.LOCAL_BROADCAST");

// 3. 注册本地广播(无需在Manifest中声明)
localBroadcastManager.registerReceiver(localReceiver, filter);

// 4. 发送本地广播
Intent intent = new Intent("com.example.LOCAL_BROADCAST");
intent.putExtra("msg", "这是应用内本地广播");
localBroadcastManager.sendBroadcast(intent);

// 5. 注销本地广播(避免内存泄漏)
localBroadcastManager.unregisterReceiver(localReceiver);

// 自定义本地广播接收器
class LocalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 处理应用内广播逻辑
        String msg = intent.getStringExtra("msg");
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    }
}

适用场景:仅需要应用内组件交互的场景,如Activity通知Service执行任务、Fragment与Activity通信等,优先推荐使用。

三、必记的系统广播:监听系统状态变化

安卓系统会在发生特定事件时,自动发送系统广播,开发者只需注册对应的接收器,就能监听系统状态变化。以下是开发中最常用的系统广播(含Action和用途),建议收藏:

系统操作 广播Action 备注
系统开机完成 android.intent.action.BOOT_COMPLETED 需申请RECEIVE_BOOT_COMPLETED权限
网络状态变化 android.net.conn.CONNECTIVITY_CHANGE Android 7.0后需动态注册
电量变化 android.intent.action.BATTERY_CHANGED 无需申请权限,可获取电量信息
屏幕锁屏/解锁 android.intent.action.SCREEN_OFF/SCREEN_ON 需动态注册
应用安装/卸载 android.intent.action.PACKAGE_ADDED/PACKAGE_REMOVED 需设置dataScheme为package
飞行模式切换 android.intent.action.AIRPLANE_MODE_CHANGED 可通过Extra获取飞行模式状态

注意:随着安卓版本迭代,部分系统广播的行为会发生变化,如Android 9.0后,网络广播不再包含位置相关信息;Android 14后,应用处于缓存状态时,系统会延迟非重要广播的传递。

四、实战避坑:这些错误千万别犯

很多开发者用不好广播,不是因为不懂原理,而是踩了一些常见坑点,以下是开发中高频错误及解决方案,一定要避开:

坑点1:onReceive()中做耗时操作,导致ANR

广播接收器的生命周期极短------仅在onReceive()方法执行期间处于活跃状态,该方法执行完毕后,接收器立即销毁。如果在onReceive()中做耗时操作(如网络请求、数据库读写),会导致主线程阻塞,触发ANR(应用无响应)。

解决方案:耗时操作交给Service(如IntentService)处理,onReceive()中仅启动Service,不执行具体逻辑。

坑点2:动态广播未注销,导致内存泄漏

动态注册的广播接收器,若未在应用组件销毁(如Activity.onStop()、Service.onDestroy())时注销,会导致接收器持有组件引用,无法被GC回收,最终造成内存泄漏。

解决方案:严格遵循"注册-注销"成对出现,Activity中建议在onStart()注册、onStop()注销;Service中在onCreate()注册、onDestroy()注销。

坑点3:Android 8.0后,静态注册接收不到自定义广播

Android 8.0(API 26)起,系统禁止静态注册隐式广播(未明确指定接收方包名的广播),仅少数系统广播例外。如果仍用静态注册接收自定义广播,会导致接收失败。

解决方案:① 改用动态注册;② 发送广播时,通过setPackage()指定接收方包名(显式广播),即使静态注册也能接收。

坑点4:跨应用广播安全隐患,导致数据泄露

全局广播可跨应用传递,若未做权限控制,可能被恶意应用监听或伪造,导致广播内容泄露、业务逻辑被篡改。

解决方案:① 优先使用本地广播;② 发送/接收广播时添加权限验证(sendBroadcast(intent, permission));③ 显式指定广播接收方包名。

坑点5:广播传递大数据,导致崩溃

广播通过Intent携带数据时,Intent的传输有大小限制(约1MB),若传递大数据(如高清图片、大字符串),会导致TransactionTooLargeException异常,应用崩溃。

解决方案:避免通过广播传递大数据,可改用文件存储、ContentProvider或EventBus等方式传递。

五、总结:广播的正确使用姿势

安卓广播的核心价值是"跨组件、跨应用异步通信",但它不是万能的,使用时需遵循以下原则:

  1. 优先选择合适的广播类型:应用内通信用本地广播,跨应用用全局广播(加权限控制),需顺序处理用有序广播;

  2. 注册方式优先选动态注册:Android 8.0以上,除少数系统广播外,避免使用静态注册;

  3. 严格遵守生命周期规范:动态广播必须成对注册/注销,onReceive()中不做耗时操作;

  4. 注意系统版本适配:不同安卓版本对广播的限制不同,需针对性适配(如Android 8.0隐式广播限制、Android 9.0网络广播变化);

  5. 避免滥用广播:若仅需应用内组件通信,且对延迟敏感,可考虑EventBus、LiveData等更轻量的方案,广播更适合监听系统事件或跨应用通信。

广播看似简单,但想要用得规范、避免踩坑,需要吃透它的原理、分类和系统限制。希望这篇博客能帮你理清安卓广播的核心知识点,在实际开发中少走弯路。如果有其他疑问,欢迎在评论区交流~

相关推荐
爱棋笑谦1 小时前
热部署简述
java
码完就睡1 小时前
C语言——结构体的内存存储规则
c语言·开发语言
磊 子1 小时前
1.2内存的存储金字塔
java·开发语言·spring·操作系统
wjs20241 小时前
Bootstrap5 提示框(Tooltip)
开发语言
逆境不可逃1 小时前
Hello-Agents 第二部分-第四章总结:智能体经典范式构建-包含习题解析和Java版
java·开发语言·javascript·人工智能·分布式·agent
springXu1 小时前
windows arm64上的VS CODE的GoLang环境的搭建
开发语言·后端·golang
ChoSeitaku1 小时前
08_抽象_接口_final关键字_多态
java·开发语言
程序员zgh1 小时前
AUTOSAR CP 之 配置、开发流程、工具链 解析
c语言·开发语言·c++·系统架构·汽车
xyq20241 小时前
Bootstrap4 提示框
开发语言