Android 应用可以发送和接收来自 Android 系统及其他 Android 应用的广播消息,这类似于发布-订阅设计模式。系统和应用通常在发生特定事件时发送广播。例如,Android 系统会在发生各种系统事件(如系统启动或设备充电)时发送广播。应用也会发送自定义广播,例如通知其他应用它们可能感兴趣的内容(例如,新数据下载)。
应用可以注册以接收特定的广播。当广播发送时,系统会自动将广播路由给已订阅接收该特定类型广播的应用。
一般来说,广播可以用作跨应用以及正常用户流程之外的消息传递系统。但是,您必须注意不要滥用响应广播的机会并在后台运行作业,这可能会导致系统性能下降。
注意:系统会优化广播的发送,以维持最佳的系统运行状况。因此,广播的送达时间无法保证。需要低延迟进程间通信的应用应考虑使用绑定服务 (bound services)。
关于系统广播
当发生各种系统事件(例如系统切换飞行模式)时,系统会自动发送广播。所有已订阅的应用都会收到这些广播。
Intent 对象封装了广播消息。action 字符串标识了发生的事件,例如 android.intent.action.AIRPLANE_MODE。Intent 也可能包含捆绑在额外字段(extra field)中的其他信息。例如,飞行模式的 Intent 包含一个布尔值 extra,用于指示飞行模式是否开启。
有关如何读取 Intent 以及从 Intent 获取 Action 字符串的详细信息,请参阅Intent 和 Intent 过滤器。
系统广播 Action
有关系统广播 Action 的完整列表,请参阅 Android SDK 中的 BROADCAST_ACTIONS.TXT 文件。每个广播 Action 都关联一个常量字段。例如,常量 ACTION_AIRPLANE_MODE_CHANGED 的值是 android.intent.action.AIRPLANE_MODE。每个广播 Action 的文档均可在其关联的常量字段中找到。
系统广播的变更
随着 Android 平台的演进,系统广播的行为会定期发生变化。请记住以下变更,以支持所有版本的 Android。
Android 16
在 Android 16 中,跨不同进程使用 android:priority 属性或 IntentFilter.setPriority() 的广播发送顺序将不再得到保证。广播优先级仅在同一个应用进程内有效,而不会跨所有进程生效。
此外,广播优先级会自动限制在 (SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1) 范围内。只有系统组件才能将 SYSTEM_LOW_PRIORITY 和 SYSTEM_HIGH_PRIORITY 设置为广播优先级。
Android 14
当应用处于缓存状态时,系统会为系统运行状况优化广播发送。例如,当应用处于缓存状态时,系统会延迟不太重要的系统广播,例如 ACTION_SCREEN_ON。一旦应用从缓存状态进入活跃进程生命周期,系统就会发送任何已延迟的广播。
在清单中声明的重要广播会暂时将应用从缓存状态中移除,以便进行发送。
Android 9
从 Android 9(API 级别 28)开始,NETWORK_STATE_CHANGED_ACTION 广播不再接收有关用户位置或个人可识别数据的信息。
如果您的应用安装在运行 Android 9.0(API 级别 28)或更高版本的设备上,系统不会在 Wi-Fi 广播中包含 SSID、BSSID、连接信息或扫描结果。要获取此信息,请改用 getConnectionInfo()。
Android 8.0
从 Android 8.0(API 级别 26)开始,系统对清单声明的接收器施加了额外的限制。
如果您的应用以 Android 8.0 或更高版本为目标,则不能使用清单来声明大多数隐式广播(非专门针对您应用的广播)的接收器。当用户正在积极使用您的应用时,您仍然可以使用上下文注册的接收器。
Android 7.0
Android 7.0(API 级别 24)及更高版本不再发送以下系统广播
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO
此外,以 Android 7.0 及更高版本为目标的应用必须使用 registerReceiver(BroadcastReceiver, IntentFilter) 注册 CONNECTIVITY_ACTION 广播。在清单中声明接收器将无法工作。
接收广播
应用可以通过两种方式接收广播:通过上下文注册的接收器和清单声明的接收器。
上下文注册的接收器
只要注册的上下文有效,上下文注册的接收器就会接收广播。这通常是在调用 registerReceiver 和 unregisterReceiver 之间的时间段。当系统销毁相应的上下文时,注册上下文也会失效。例如,如果您在 Activity 上下文中注册,只要 Activity 保持活跃,您就会收到广播。如果您使用 Application 上下文进行注册,只要应用在运行,您就会收到广播。
要使用上下文注册接收器,请执行以下步骤
在应用的模块级构建文件中,包含 1.9.0 或更高版本的 AndroidX Core 库
Groovy
dependencies {
def core_version = "1.19.0"
// Java language implementation
implementation "androidx.core:core:$core_version"
// Kotlin
implementation "androidx.core:core-ktx:$core_version"
// To use RoleManagerCompat
implementation "androidx.core:core-role:1.1.0"
// To use the Animator APIs
implementation "androidx.core:core-animation:1.0.0"
// To test the Animator APIs
androidTestImplementation "androidx.core:core-animation-testing:1.0.0"
// Optional - To enable APIs that query the performance characteristics of GMS devices.
implementation "androidx.core:core-performance:1.0.0"
// Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
implementation "androidx.core:core-google-shortcuts:1.1.0"
// Optional - to support backwards compatibility of RemoteViews
implementation "androidx.core:core-remoteviews:1.1.0"
// Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
implementation "androidx.core:core-splashscreen:1.2.0"
}
Kotlin
dependencies {
val core_version = "1.19.0"
// Java language implementation
implementation("androidx.core:core:$core_version")
// Kotlin
implementation("androidx.core:core-ktx:$core_version")
// To use RoleManagerCompat
implementation("androidx.core:core-role:1.1.0")
// To use the Animator APIs
implementation("androidx.core:core-animation:1.0.0")
// To test the Animator APIs
androidTestImplementation("androidx.core:core-animation-testing:1.0.0")
// Optional - To enable APIs that query the performance characteristics of GMS devices.
implementation("androidx.core:core-performance:1.0.0")
// Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
implementation("androidx.core:core-google-shortcuts:1.1.0")
// Optional - to support backwards compatibility of RemoteViews
implementation("androidx.core:core-remoteviews:1.1.0")
// Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
implementation("androidx.core:core-splashscreen:1.2.0")
}
创建 BroadcastReceiver 的实例
Kotlinval myBroadcastReceiver = MyBroadcastReceiver()
JavaMyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
创建 IntentFilter 的实例
Kotlinval filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
JavaIntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");
选择是否应导出广播接收器并使其对设备上的其他应用可见。如果此接收器正在监听来自系统或其他应用(即使是您拥有的其他应用)发送的广播,请使用 RECEIVER_EXPORTED 标志。如果此接收器仅监听由您的应用发送的广播,请使用 RECEIVER_NOT_EXPORTED 标志。
某些系统广播来自高度特权的应用(例如蓝牙和电话),它们是 Android 框架的一部分,但不在系统的唯一进程 ID (UID) 下运行。要接收所有系统广播(包括来自高度特权应用的广播),请使用 RECEIVER_EXPORTED 标记您的接收器。
如果您使用 RECEIVER_NOT_EXPORTED 标记您的接收器,该接收器能够接收某些系统广播和来自您的应用的广播,但不能接收来自高度特权应用的广播。
如果您的应用正在监听多个广播,但其中一些应该标记为 RECEIVER_NOT_EXPORTED,而另一些应标记为 RECEIVER_EXPORTED,请将广播分配给不同的广播接收器。
Kotlinval listenToBroadcastsFromOtherApps = false
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
ContextCompat.RECEIVER_EXPORTED
} else {
ContextCompat.RECEIVER_NOT_EXPORTED
}BroadcastReceiverSnippets.kt
Javaboolean listenToBroadcastsFromOtherApps = false;
int receiverFlags = listenToBroadcastsFromOtherApps
? ContextCompat.RECEIVER_EXPORTED
: ContextCompat.RECEIVER_NOT_EXPORTED;BroadcastReceiverJavaSnippets.java
警告:如果您导出广播接收器,其他应用可能会向您的应用发送未受保护的广播。
通过调用 registerReceiver() 注册接收器
KotlinContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)
JavaContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);
要停止接收广播,请调用 unregisterReceiver(android.content.BroadcastReceiver)。请确保在不再需要接收器或上下文不再有效时取消注册。
取消注册您的广播接收器
在注册广播接收器时,它会持有您注册它的上下文的引用。如果接收器的注册范围超过了上下文生命周期范围,这可能会导致内存泄漏。例如,当您在 Activity 范围内注册接收器,但忘记在系统销毁 Activity 时取消注册时,就会发生这种情况。因此,请务必取消注册您的广播接收器。
Kotlinclass MyActivity : ComponentActivity() {
private val myBroadcastReceiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags)
setContent { MyApp() }
}
override fun onDestroy() {
super.onDestroy()
// When you forget to unregister your receiver here, you're causing a leak!
this.unregisterReceiver(myBroadcastReceiver)
}
}BroadcastReceiverSnippets.kt
Javaclass MyActivity extends ComponentActivity {
MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags);
// Set content
}
}BroadcastReceiverJavaSnippets.java
在最小范围内注册接收器
您的广播接收器应该仅在您确实对结果感兴趣时才注册。选择尽可能小的接收器范围
LifecycleResumeEffect 或 Activity 的 onResume/onPause 生命周期方法:广播接收器仅在应用处于恢复 (resumed) 状态时接收更新。
LifecycleStartEffect 或 Activity 的 onStart/onStop 生命周期方法:广播接收器仅在应用处于恢复状态时接收更新。
DisposableEffect:广播接收器仅在可组合项位于组合树中时接收更新。此范围未附加到 Activity 生命周期范围。考虑在应用程序上下文中注册接收器,因为从理论上讲,可组合项的生命周期可能超过 Activity 生命周期,从而导致 Activity 泄漏。
Activity 的 onCreate/onDestroy:广播接收器在 Activity 处于创建状态时接收更新。请确保在 onDestroy() 中而不是 onSaveInstanceState(Bundle) 中取消注册,因为后者可能不会被调用。
自定义范围:例如,您可以在 ViewModel 范围内注册接收器,使其在 Activity 重建后仍然存在。请确保使用应用程序上下文来注册接收器,因为接收器的生命周期可能会超过 Activity 生命周期范围,从而导致 Activity 泄漏。
创建有状态和无状态的可组合项
Compose 具有有状态和无状态的可组合项。在可组合项内部注册或取消注册广播接收器会使其成为有状态的。该可组合项不再是一个在传递相同参数时呈现相同内容的确定性函数。内部状态可能会根据对已注册广播接收器的调用而改变。
作为 Compose 的最佳实践,我们建议您将可组合项拆分为有状态和无状态版本。因此,我们建议您将广播接收器的创建过程提升 (hoist) 到可组合项之外,使其成为无状态的。
@Composable
fun MyStatefulScreen() {
val myBroadcastReceiver = remember { MyBroadcastReceiver() }
val context = LocalContext.current
LifecycleStartEffect(true) {
// ...
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags)
onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) }
}
MyStatelessScreen()
}
@Composable
fun MyStatelessScreen() {
// Implement your screen
}BroadcastReceiverSnippets.kt
清单声明的接收器
如果您在清单中声明广播接收器,当发送广播时,系统会启动您的应用。如果应用尚未运行,系统将启动该应用。
注意:如果您的应用以 API 级别 26 或更高版本为目标,则不能使用清单为隐式广播声明接收器,除非极少数被豁免的隐式广播。隐式广播是指非专门针对您应用的广播。在大多数情况下,您可以改用计划作业。
要在清单中声明广播接收器,请执行以下步骤
在应用的清单中指定
AndroidManifest.xml
Intent 过滤器指定您的接收器订阅的广播 Action。
继承 BroadcastReceiver 并实现 onReceive(Context, Intent)。以下示例中的广播接收器记录并显示广播的内容。
Kotlinclass MyBroadcastReceiver : BroadcastReceiver() {
@Inject
lateinit var dataRepository: DataRepository
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") {
val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data"
// Do something with the data, for example send it to a data repository:
dataRepository.updateData(data)
}
}
}BroadcastReceiverSnippets.kt
Javapublic static class MyBroadcastReceiver extends BroadcastReceiver {
@Inject
DataRepository dataRepository;
@Override
public void onReceive(Context context, Intent intent) {
if (Objects.equals(intent.getAction(), "com.example.snippets.ACTION_UPDATE_DATA")) {
String data = intent.getStringExtra("com.example.snippets.DATA");
// Do something with the data, for example send it to a data repository:
if (data != null) { dataRepository.updateData(data); }
}
}
}BroadcastReceiverJavaSnippets.java
系统包管理器会在应用安装时注册接收器。随后,接收器成为通往您应用的独立入口点,这意味着如果应用未运行,系统可以启动应用并发送广播。
系统会创建一个新的 BroadcastReceiver 组件对象来处理它接收到的每个广播。该对象仅在调用 onReceive(Context, Intent) 的期间有效。一旦您的代码从该方法返回,系统就会认为该组件不再处于活跃状态。
对进程状态的影响
BroadcastReceiver 是否在运行会影响其所在的进程,这可能会改变其被系统杀死的可能性。前台进程会执行接收器的 onReceive() 方法。除非内存压力极大,否则系统会运行该进程。
系统会在 onReceive() 之后停用 BroadcastReceiver。接收器宿主进程的重要性取决于其包含的应用组件。如果该进程仅托管一个清单声明的接收器,系统可能会在 onReceive() 之后杀死它,以释放资源给其他更关键的进程。这对于用户从未交互或近期未交互的应用很常见。
因此,广播接收器不应启动长时间运行的后台线程。系统可以在 onReceive() 之后的任何时刻停止该进程以回收内存,从而终止所创建的线程。要保持进程存活,请使用 JobScheduler 从接收器调度 JobService,以便系统知道该进程仍在工作。后台工作概览提供了更多详细信息。
发送广播
Android 提供了两种应用发送广播的方式
sendOrderedBroadcast(Intent, String) 方法一次向一个接收器发送广播。当每个接收器依次执行时,它可以将结果传播给下一个接收器。它还可以完全中止广播,使其无法到达其他接收器。您可以控制在同一应用进程内运行接收器的顺序。为此,请使用匹配的 Intent 过滤器的 android:priority 属性。具有相同优先级的接收器将以任意顺序运行。
sendBroadcast(Intent) 方法以未定义的顺序向所有接收器发送广播。这称为普通广播。这种方式效率更高,但意味着接收器无法读取来自其他接收器的结果、传播从广播接收到的数据或中止广播。
以下代码片段演示了如何通过创建 Intent 并调用 sendBroadcast(Intent) 来发送广播。
Kotlinval intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
putExtra("com.example.snippets.DATA", newData)
setPackage("com.example.snippets")
}
context.sendBroadcast(intent)BroadcastReceiverSnippets.kt
JavaIntent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA");
intent.putExtra("com.example.snippets.DATA", newData);
intent.setPackage("com.example.snippets");
context.sendBroadcast(intent);BroadcastReceiverJavaSnippets.java
广播消息封装在 Intent 对象中。Intent 的 action 字符串必须提供应用的 Java 包名语法,并唯一标识广播事件。您可以使用 putExtra(String, Bundle) 将额外信息附加到 Intent。您还可以通过在 Intent 上调用 setPackage(String),将广播限制为同一组织内的一组应用。
注意:尽管您可以使用 Intent 来发送广播和通过 startActivity(Intent) 启动 Activity,但这些操作完全不相关。广播接收器无法看到或捕获用于启动 Activity 的 Intent;同样,当您广播 Intent 时,您无法找到或启动 Activity。
使用权限限制广播
权限允许您将广播限制为持有特定权限的应用集。您可以对广播的发送者或接收者强制执行限制。
使用权限发送广播
当您调用 sendBroadcast(Intent, String) 或 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) 时,您可以指定一个权限参数。只有在清单中使用
Kotlincontext.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)
Javacontext.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION);
要接收该广播,接收应用必须按如下方式请求权限
您可以指定现有的系统权限(如 BLUETOOTH_CONNECT),也可以使用
注意:自定义权限在应用安装时注册。定义自定义权限的应用必须先于使用该权限的应用安装。
使用权限接收广播
如果您在注册广播接收器时指定了权限参数(无论是通过 registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 还是在清单中的
例如,假设您的接收应用在清单中声明了接收器,如下所示
android:name=".MyBroadcastReceiverWithPermission" android:permission="android.permission.ACCESS_COARSE_LOCATION" android:exported="true">
或者您的接收应用具有如下上下文注册的接收器
KotlinContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
)BroadcastReceiverSnippets.kt
JavaContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // scheduler that defines thread, null means run on main thread
receiverFlags
);BroadcastReceiverJavaSnippets.java
然后,为了能够向这些接收器发送广播,发送应用必须按如下方式请求权限
安全考虑
以下是发送和接收广播的一些安全注意事项
如果许多应用在清单中注册以接收相同的广播,可能会导致系统启动大量应用,从而对设备性能和用户体验产生重大影响。为避免这种情况,请优先使用上下文注册而非清单声明。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播仅发送给上下文注册的接收器。
不要使用隐式 Intent 广播敏感信息。任何应用如果注册接收该广播,都可以读取其中的信息。有三种方法可以控制谁能接收您的广播
您可以在发送广播时指定权限。
在 Android 4.0(API 级别 14)及更高版本中,您可以在发送广播时通过 setPackage(String) 指定一个 包名。系统会将广播限制为与该包匹配的应用集。
当您注册接收器时,任何应用都可以向您的应用接收器发送潜在的恶意广播。有几种方法可以限制您的应用接收的广播
您可以在注册广播接收器时指定权限。
对于清单声明的接收器,您可以在清单中将 android:exported 属性设置为 "false"。这样,接收器将不会从应用外部接收广播。
广播 Action 的命名空间是全局的。请确保 Action 名称和其他字符串是在您拥有的命名空间中编写的。否则,您可能会无意中与其他应用发生冲突。
由于接收器的 onReceive(Context, Intent) 方法在主线程上运行,因此它应该快速执行并返回。如果您需要执行长时间运行的工作,请谨慎使用生成线程或启动后台服务,因为系统可能会在 onReceive() 返回后杀死整个进程。有关更多信息,请参阅对进程状态的影响。要执行长时间运行的工作,我们建议:
在接收器的 onReceive() 方法中调用 goAsync(),并将 BroadcastReceiver.PendingResult 传递给后台线程。这使广播在从 onReceive() 返回后保持活跃状态。但是,即使采用这种方法,系统也希望您非常快地(10 秒内)完成广播处理。它确实允许您将工作移至另一个线程以避免阻塞主线程。
使用 JobScheduler 调度作业。有关更多信息,请参阅智能作业调度。
不要从广播接收器启动 Activity,因为这会打断用户体验;特别是在有多个接收器的情况下。相反,请考虑显示通知。