我的安卓第一课:四大组件之一Broadcast

引言

什么是广播

初次听到"广播"这个词时,可能会觉得有些抽象。但如果联想到 IP 协议中的广播机制,或者现实生活中的广播系统------即向特定范围内所有接收者发送一条统一信息的方式,就不难理解其核心含义了。

在 Android 中,广播机制正是基于这一思想构建的:它是一种基于事件驱动的通信方式,允许系统或应用程序发送一条消息(称为"广播"),通知其他组件某些事件的发生。这些事件可以来源于用户的操作、系统状态的变化(如网络连接、电量变化等),也可以是应用内部的业务逻辑触发。

通过注册广播接收器(BroadcastReceiver),应用组件可以在不直接耦合的前提下监听并响应这些事件,从而实现松耦合、高内聚的通信结构。这种机制为组件间协作、跨应用通信以及系统级事件感知提供了强大的支持。

广播能做什么

当系统状态发生改变时,Android 系统会主动发送一系列广播(Broadcast),用于通知应用当前设备环境的变化。通过注册相应的 BroadcastReceiver,应用可以监听这些广播并作出响应,从而实现对系统事件的感知和行为调整。

获取系统状态

以下是几个常见的系统广播示例:

  1. 设备启动完成。当系统完成启动后,会发出 ACTION_BOOT_COMPLETED 广播。应用可以通过监听该广播,在设备重启后执行初始化操作或启动后台服务。

⚠️ 注意:需要声明权限:

Xml 复制代码
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  1. 网络连接状态变化。通过监听 CONNECTIVITY_ACTION 广播,应用可以实时感知网络状态的变化,例如 Wi-Fi 连接、移动数据切换等。这为在网络状态变更时进行数据同步或提示用户提供了基础支持。

  2. 电池电量变化。Android 提供了多个与电池状态相关的广播,如 ACTION_BATTERY_LOW(电量低)和 ACTION_BATTERY_OKAY(电量恢复)。应用可以根据当前电量情况动态调整资源使用策略,例如降低刷新频率或提示用户充电。

  3. 时间变化事件。系统还提供了一些与时间相关的广播,例如每分钟触发一次的 TIME_TICK。开发者可以利用此类广播实现定时提醒、日历更新、按时间展示不同内容等功能

跨应用通信

应用之间可以通过广播机制实现跨进程通信。通过发送一条特定的广播消息,一个应用可以将信息传递给另一个应用;只要目标应用对该广播的 ACTION 进行了监听并注册了相应的 BroadcastReceiver,就可以接收到该广播并作出响应。这种方式为跨应用通信提供了一种轻量级且灵活的解决方案。

不过,使用广播进行跨应用通信的前提是:双方应用必须事先约定相同的广播 Action,并且在必要时声明和授予相应的自定义权限,以确保通信的安全性和可控性。

应用内通信

Android 中的广播机制不仅可用于跨应用通信,同样也适用于应用内部不同组件之间的通信 。通过在应用内发送和接收广播,组件之间可以实现松耦合 的交互方式,无需彼此持有引用 即可完成事件通知与数据传递。这种方式特别适用于需要多个组件监听同一事件、全局状态变更通知等场景=。

不推荐发送大量数据与高频通信

使用广播

在 Android 中,广播的使用主要涉及到两个方面:发送广播和接收广播 。接收广播可以通过两种方式来注册广播接收器(BroadcastReceiver):动态注册和静态注册 。发送广播则分为:发送经典广播与有序广播

接收广播

动态注册监听

动态注册是指在程序运行过程中,通过代码手动注册广播接收器的方式 。这种方式具有更高的灵活性,且从 Android 8.0(API 26)开始,系统对隐式广播的静态注册进行了限制,因此动态注册成为当前推荐的主要方式

第一步:需要实现一个广播接收器 ,继承BroadReceiver,实现onReceive()

java 复制代码
public class TimeChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "时间发生变化", Toast.LENGTH_SHORT).show();
    }
}

onReceive() 方法会在接收到匹配的广播时被调用,注意不要在此执行耗时操作,否则可能导致 ANR。

第二步:在 ActivityService 中注册广播接收器

java 复制代码
binding.buttonRegisterReceiver.setOnClickListener(v -> {
    IntentFilter filter = new IntentFilter();
    // 需要过滤器过滤
    filter.addAction("android.intent.action.TIME_TICK");
    timeChangeReceiver = new TimeChangeReceiver();
    // 当注册的非系统广播时,需要填写是否允许外部应用触发
    // registerReceiver(myTimeBroadcast, filter,  RECEIVER_NOT_EXPORTED);
    registerReceiver(timeChangeReceiver, filter);
});

之后,每过一分钟,应用就会收到一个消息,使用timeChangeReceiver.onReceive(...)接收

需要注意的是,动态注册的广播接收器必须在不再需要时手动注销 ,以避免内存泄漏。通常建议在组件销毁前(如 onDestroy())调用 unregisterReceiver()

静态注册监听

动态注册通过编写代码实现,具有高度的灵活性,尤其适用于运行时根据需求动态控制广播接收器的场景。然而,它也存在一个根本性的限制:只有在应用启动并执行注册操作后,才能开始监听广播。这意味着如果应用未处于活跃状态,将无法接收到相关事件。

与之相对,静态注册不依赖于应用是否启动 ,即使应用当前未运行,只要系统发出匹配的广播,接收器仍会被激活并处理该广播。这使得静态注册非常适合用于监听系统级事件(如设备启动完成、电池状态变化等),在这些场景下,要求应用在特定系统事件发生时能够被自动唤醒并作出响应。

第一步:AndroidManifest.xml注册reveiver

xml 复制代码
<receiver
    android:name=".broadcast.MyStaticReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

第二步:AndroidManifest.xml申请权限 ( 有时需要 )

xml 复制代码
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

第三步:编写对应接收器

java 复制代码
public class MyStaticReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "设备启动完成", Toast.LENGTH_LONG).show();
    }
}

onReceive() 方法会在接收到匹配的广播时被调用,注意不要在此执行耗时操作,否则可能导致 ANR。

发送

在 Android 中,除了接收广播,应用也可以主动发送广播,以通知其他组件或应用某个事件的发生。根据广播的传递方式和特性,Android 提供了多种广播类型。如下两种

标准广播

标准广播是一种异步广播,系统会将广播同时发送给所有匹配的 BroadcastReceiver。各个接收器之间是并行接收的,彼此之间无法相互影响,也没有优先级之分。

  1. 先注册接收器用于测试
java 复制代码
binding.registerButton.setOnClickListener(view -> {
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.example.hellocast.SEND");
    // 允许外部触发
    registerReceiver(myBroadcast, filter,  RECEIVER_EXPORTED);
    Toast.makeText(this, "MyBroadcast", Toast.LENGTH_SHORT).show();
});
  1. 发送广播
java 复制代码
binding.sendButton.setOnClickListener(view -> {
    Intent intent = new Intent("com.example.hellocast.SEND");
    // 设置指定目标包名,可将隐形广播变为显性广播
    // intent.setPackage(getPackageName());
    intent.putExtra("message", "这是一条标准广播");
    sendBroadcast(intent);
});
  1. 结果
text 复制代码
com.example.hellocast                D  onReceive: com.example.hellocast, 这是一条标准广播

上面有许多注意的点

  1. 静态注册的 BroadcastReceiver 是无法接收隐式广播的,除非携带包名,使得隐式广播变显式广播。(可以自己写代码尝试一下,会发现如果不携带包名,只有动态注册的接收器可以接收广播)
  2. 动态注册接收器无此限制,但当 registerReceiver 的第三个参数为 RECEIVER_NOT_EXPORTED 时,也必须要携带包名才能触发

有序广播

经典广播的广播是并行 的,只要符合要求的接收者都能接收。而有序广播是串行的,必须前接收器允许向后传输,后接收器才能接收到。

发送广播

只需要将sendBroadcast改为sendOrderedBroadcast第二个参数为所要求的权限

java 复制代码
binding.sendButton.setOnClickListener(view -> {
    Intent intent = new Intent("com.example.hellocast.SEND");
    // 转为显式
    intent.setPackage(getPackageName());
    intent.putExtra("message", "这是一条标准广播");
    sendOrderedBroadcast(intent, null);
});

接收广播

实际上广播接收器不进行修改就可以使用。不过这就体现不出有序广播的特点(按优先级广播 )了。如果需要设置优先级,可以使用filter.setPriority(100);<intent-filter android:priority="1000">。例如 动态注册

java 复制代码
binding.registerButton.setOnClickListener(view -> {
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.example.hellocast.SEND");
    filter.setPriority(100);  // 设置优先级
    registerReceiver(myBroadcast, filter,  RECEIVER_EXPORTED);
    Toast.makeText(this, "MyBroadcast", Toast.LENGTH_SHORT).show();
});

静态注册

java 复制代码
<receiver
    android:name=".broadcast.MyReceiver2"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="1000">
        <action android:name="com.example.hellocast.SEND" />
    </intent-filter>
</receiver>

如果需要中止有序广播的向后传播,可以使用abortBroadcast();来中止

java 复制代码
@Override
public void onReceive(Context context, Intent intent) {
    Log.d(TAG, "onReceive: com.example.hellocast.SEND");
    abortBroadcast();
}

广播的权限机制

在安卓系统中,曾经出现过许多"恶意软件"利用广播机制来"作恶"的现象,某些"恶意软件"可能利用广播机制来监听敏感信息、启动未经授权的服务或执行其他形式的攻击。为了防止这种情况的发生,Android 提供了严格的权限机制来控制广播的发送和接收

系统广播

在系统广播中,由于部分系统广播往往涉及到重要信息,故大部分情况下,接收系统广播都需要申请权限 。例如,要接收设备启动完成后的广播 (ACTION_BOOT_COMPLETED),你需要在 AndroidManifest.xml 文件中添加以下权限声明:

xml 复制代码
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

具体可以查阅developer.android.google.cn/reference/a...

自定义广播

除了接收系统广播外,开发者还可以创建自己的广播来实现应用内部或跨应用之间的通信。为了确保这些广播的安全性,可以通过设置权限来限制哪些应用可以发送或接收它们。

发送广播时指定权限

当你发送一个自定义广播时,可以通过 sendBroadcast(intent, receiverPermission) 方法来限制接收者的权限。例如:

Java 复制代码
Intent intent = new Intent("com.example.CUSTOM_ACTION");
intent.putExtra("message", "这是一个受保护的广播");

// 只有拥有 com.example.permission.RECEIVE_CUSTOM_BROADCAST 权限的应用才能接收
sendBroadcast(intent, "com.example.permission.RECEIVE_CUSTOM_BROADCAST");

接收广播时声明权限

为了让应用能够接收带有权限要求的广播,你必须在 AndroidManifest.xml 文件中声明所需的权限:

xml 复制代码
<uses-permission android:name="com.example.permission.RECEIVE_CUSTOM_BROADCAST"/>

并且,在注册广播接收器时,可以在 标签中指定权限:

xml 复制代码
<receiver android:name=".MyCustomReceiver"
          android:permission="com.example.permission.RECEIVE_CUSTOM_BROADCAST">
    <intent-filter>
        <action android:name="com.example.CUSTOM_ACTION" />
    </intent-filter>
</receiver>

附录

项目 动态注册 静态注册
注册位置 Java/Kotlin 代码中 AndroidManifest.xml
是否需要应用启动
生命周期控制 手动管理(注册/注销) 系统自动激活
兼容性 支持所有广播类型 部分隐式广播受限于 Android 8.0+
推荐用途 运行时监听、组件间通信 监听系统事件、跨应用广播
安全性 更高(生命周期可控) 稍低(可能被其他应用触发)
组合方式 说明 推荐程度
显式广播 + 动态注册 安全、灵活,推荐用于应用内部通信 ✅ 强烈推荐
隐式广播 + 动态注册 安全且兼容性好,是 Android 8.0+ 推荐方式 ✅ 强烈推荐
显式广播 + 静态注册 不常见,显式广播一般不需要静态注册 ⚠️ 少数情况可用
隐式广播 + 静态注册 从 Android 8.0 开始限制较多,仅几个系统广播可用,建议避免 ❌ 不推荐

系统广播

安卓系统广播列表:<Android SDK>/platforms/<任意android api版本>/data/broadcast_actions.txt

在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了 。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收 。这些特殊的系统广播列表详见developer.android.google.cn/develop/bac...

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui