广播概览

Android 应用会发送和接收来自 Android 系统和其他 Android 应用的广播消息,类似于发布-订阅设计模式。系统和应用通常会在发生某些事件时发送广播。例如,当发生各种系统事件(例如系统启动或设备充电)时,Android 系统会发送广播。应用还会发送自定义广播,例如,通知其他应用它们可能感兴趣的内容(例如,新数据下载)。

应用可以注册接收特定的广播。发送广播时,系统会自动将广播路由到已订阅接收相应类型广播的应用。

一般来说,广播可用作应用之间以及正常用户流程之外的消息传递系统。不过,您必须注意不要滥用响应广播和在后台运行作业的机会,以免导致系统性能下降。

关于系统广播

当发生各种系统事件时(例如系统切换到飞行模式或从飞行模式切换出来),系统会自动发送广播。所有已订阅的应用都会收到这些广播。

Intent 对象封装了广播消息。action 字符串用于标识发生的事件,例如 android.intent.action.AIRPLANE_MODE。intent 也可能包含捆绑到其 extra 字段中的其他信息。例如,飞行模式 intent 包含一个布尔值 extra,用于指示飞行模式是否处于开启状态。

如需详细了解如何读取 intent 并从 intent 中获取操作字符串,请参阅 intent 和 intent 过滤器。

系统广播操作

如需查看系统广播操作的完整列表,请参阅 Android SDK 中的 BROADCAST_ACTIONS.TXT 文件。每个广播操作都有一个关联的常量字段。例如,常量 ACTION_AIRPLANE_MODE_CHANGED 的值为 android.intent.action.AIRPLANE_MODE。每个广播操作的文档都可在其关联的常量字段中找到。

系统广播所发生的更改

随着 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)或更高版本的设备上,系统不会在 WLAN 广播中包含 SSID、BSSID、连接信息或扫描结果。如需获取此信息,请改为调用 getConnectionInfo()。

Android 8.0

从 Android 8.0(API 级别 26)开始,系统对清单声明的接收器施加了额外的限制。

如果您的应用以 Android 8.0 或更高版本为目标平台,则无法使用清单来声明大多数隐式广播(并非专门针对您的应用的广播)的接收器。当用户积极使用您的应用时,您仍然可以使用上下文注册的接收器。

Android 7.0

Android 7.0(API 级别 24)及更高版本不会发送以下系统广播:

此外,以 Android 7.0 及更高版本为目标平台的应用必须使用 registerReceiver(BroadcastReceiver, IntentFilter) 注册 CONNECTIVITY_ACTION 广播。在清单中声明接收器不起作用。

接收广播

应用可以通过以下两种方式接收广播:通过上下文注册的接收器和清单声明的接收器。

上下文注册的接收器

只要注册上下文有效,上下文注册的接收器就会收到广播。这通常发生在对 registerReceiver 和 unregisterReceiver 的调用之间。当系统销毁相应上下文时,注册上下文也会失效。例如,如果您在 Activity 上下文中注册,那么只要 activity 保持活跃状态,您就会收到广播。如果您使用应用上下文注册,则只要应用在运行,您就会收到广播。

要使用上下文注册接收器,请执行以下步骤:

  1. 在应用的模块级 build 文件中,添加 AndroidX Core 库 1.9.0 版或更高版本:

    Groovy

    dependencies {
        def core_version = "1.19.1"
    
        // 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.1"
    
        // 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")
    }
  2. 创建 BroadcastReceiver 的实例:

    Kotlin

    val myBroadcastReceiver = MyBroadcastReceiver()
    

    Java

    MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
    
  3. 创建 IntentFilter 的实例:

    Kotlin

    val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
    

    Java

    IntentFilter filter = new IntentFilter("com.example.snippets.ACTION_UPDATE_DATA");
    
  4. 选择广播接收器是否应被导出以及是否对设备上的其他应用可见。如果此接收器正在监听来自系统或其他应用(即使是您自己的其他应用)的广播,请使用 RECEIVER_EXPORTED 标志。如果此接收器仅监听由您的应用发送的广播,请使用 RECEIVER_NOT_EXPORTED 标志。

    Kotlin

    val listenToBroadcastsFromOtherApps = false
    val receiverFlags = if (listenToBroadcastsFromOtherApps) {
        ContextCompat.RECEIVER_EXPORTED
    } else {
        ContextCompat.RECEIVER_NOT_EXPORTED
    }
    

    Java

    boolean listenToBroadcastsFromOtherApps = false;
    int receiverFlags = listenToBroadcastsFromOtherApps
            ? ContextCompat.RECEIVER_EXPORTED
            : ContextCompat.RECEIVER_NOT_EXPORTED;
    
  5. 通过调用 registerReceiver() 注册接收器:

    Kotlin

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)
    

    Java

    ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags);
    
  6. 如需停止接收广播,请调用 unregisterReceiver(android.content.BroadcastReceiver)。请务必在不再需要接收器或上下文不再有效时取消注册接收器。

取消注册广播接收器

在广播接收器注册期间,它会保留对您注册时所用 Context 的引用。如果接收器的注册范围超出 Context 生命周期范围,这可能会导致内存泄漏。例如,当您在 activity 范围内注册接收器,但忘记在系统销毁 activity 时取消注册该接收器时,可能会发生这种情况。因此,请务必始终取消注册广播接收器。

Kotlin

class 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)
    }
}

Java

class MyActivity extends ComponentActivity {
    MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
        ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags);
        // Set content
    }
}

在最小范围内注册接收器

只有在您确实对结果感兴趣时,才应注册广播接收器。选择尽可能小的接收器范围:

  • LifecycleResumeEffect 或 activity onResume/onPause 生命周期方法:广播接收器仅在应用处于已恢复状态时接收更新。
  • LifecycleStartEffect 或 activity onStart/onStop 生命周期方法:广播接收器仅在应用处于已恢复状态时接收更新。
  • DisposableEffect:仅当可组合项位于组合树中时,广播接收器才会接收更新。此范围未附加到 activity 生命周期范围。考虑在应用上下文中注册接收器。这是因为可组合项在理论上可能会比 activity 生命周期范围更长,从而导致 activity 泄漏。
  • activity onCreate/onDestroy:当 activity 处于已创建状态时,广播接收器会收到更新。请务必在 onDestroy() 中(而非 onSaveInstanceState(Bundle) 中)取消注册,因为可能不会被调用。
  • 自定义范围:例如,您可以在 ViewModel 范围内注册接收器,这样即使 activity 重新创建,接收器也能继续存在。请务必使用应用上下文来注册接收器,因为接收器的生命周期范围可能比 activity 的生命周期范围长,从而导致 activity 泄漏。

创建有状态和无状态可组合项

Compose 具有有状态可组合项和无状态可组合项。在可组合项内注册或取消注册广播接收器会使其变为有状态。可组合函数不是确定性函数,当传递相同的参数时,它不会渲染相同的内容。内部状态可能会根据对已注册的广播接收器的调用而发生变化。

在 Compose 中,我们建议的最佳实践是将可组合项拆分为有状态版本和无状态版本。因此,我们建议您将广播接收器的创建从可组合项中提升出来,以使其变为无状态:

@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
}

清单声明的接收器

如果您在清单中声明了广播接收器,系统会在发送广播时启动您的应用。如果应用尚未运行,系统会启动该应用。

要在清单中声明广播接收器,请执行以下步骤:

  1. 在应用的清单中指定 <receiver> 元素。

    <!-- If this receiver listens for broadcasts sent from the system or from
         other apps, even other apps that you own, set android:exported to "true". -->
    <receiver android:name=".MyBroadcastReceiver" android:exported="false">
        <intent-filter>
            <action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
        </intent-filter>
    </receiver>
    

    intent 过滤器用于指定接收器订阅的广播操作。

  2. 对 BroadcastReceiver 进行子类化并实现 onReceive(Context, Intent)。以下示例中的广播接收器会记录并显示广播的内容:

    Kotlin

    class 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)
            }
        }
    }
    

    Java

    public 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); }
            }
        }
    }
    

系统软件包管理系统会在应用安装时注册接收器。然后,接收器会成为应用的单独入口点,这意味着如果应用未运行,系统可以启动应用并传送广播。

系统会创建一个新的 BroadcastReceiver 组件对象来处理收到的每条广播。此对象仅在调用 onReceive(Context, Intent) 的期间有效。当代码从此方法返回后,系统会认为相应组件不再处于活跃状态。

对进程状态的影响

BroadcastReceiver 是否运行会影响其包含的进程,进而改变其被系统终止的可能性。前台进程执行接收器的 onReceive() 方法。系统会运行该进程,除非内存压力过大。

系统会在 onReceive() 后停用 BroadcastReceiver。接收器宿主进程的重要性取决于其应用组件。如果该进程仅托管清单声明的接收器,系统可能会在 onReceive() 后终止该进程,以便为其他更关键的进程释放资源。对于用户从未互动过或近期未互动过的应用,这种情况很常见。

因此,广播接收器不应启动长时间运行的后台线程。系统可以在 onReceive() 之后的任何时刻停止该进程以回收内存,从而终止创建的线程。如需保持进程处于活跃状态,请使用 JobScheduler 从接收器调度 JobService,以便系统知道该进程仍在运行。如需了解更多详情,请参阅后台工作概览。

发送广播

Android 为应用提供了两种发送广播的方式:

  • sendOrderedBroadcast(Intent, String) 方法一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递结果。它还可以完全中止广播,使其无法到达其他接收器。 您可以控制接收器在同一应用进程中运行的顺序。为此,请使用匹配的 intent-filter 的 android:priority 属性。具有相同优先级的接收器按任意顺序运行。
  • sendBroadcast(Intent) 方法会以未定义的顺序向所有接收器发送广播。这称为“正常广播”。这种方式效率更高,但意味着接收器无法读取其他接收器的结果、传播从广播接收的数据或中止广播。

以下代码段演示了如何通过创建 Intent 并调用 sendBroadcast(Intent) 来发送广播。

Kotlin

val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
    putExtra("com.example.snippets.DATA", newData)
    setPackage("com.example.snippets")
}
context.sendBroadcast(intent)

Java

Intent intent = new Intent("com.example.snippets.ACTION_UPDATE_DATA");
intent.putExtra("com.example.snippets.DATA", newData);
intent.setPackage("com.example.snippets");
context.sendBroadcast(intent);

广播消息封装在 Intent 对象中。intent 的 action 字符串必须提供应用的 Java 软件包名称语法,并唯一标识广播事件。您可以使用 putExtra(String, Bundle) 将其他信息附加到 intent。您还可以通过对 intent 调用 setPackage(String),将广播限制为同一组织中的一组应用。

通过权限限制广播

借助权限,您可以将广播限制为仅发送给拥有特定权限的应用。您可以对广播的发送者或接收者强制执行限制。

发送具有权限的广播

调用 sendBroadcast(Intent, String) 或 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) 时,您可以指定权限参数。只有在清单中使用 <uses-permission> 标记请求了该权限的接收器才能接收广播。如果权限是危险权限,您必须先授予该权限,接收器才能接收广播。例如,以下代码会发送具有权限的广播:

Kotlin

context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)

Java

context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION);

若要接收广播,接收应用必须按如下方式请求权限:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

您可以指定现有的系统权限(例如 BLUETOOTH_CONNECT),也可以使用 <permission> 元素定义自定义权限。如需了解权限和安全性(一般),请参阅系统权限。

接收具有权限的广播

如果您在注册广播接收器时指定了权限参数(通过 registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 或清单中的 <receiver> 标记),那么只有在清单中通过 <uses-permission> 标记请求了相应权限的广播方才能向该接收器发送 intent。如果权限是危险权限,还必须向广播方授予该权限。

例如,假设您的接收应用具有如下所示的清单声明的接收器:

<!-- If this receiver listens for broadcasts sent from the system or from
     other apps, even other apps that you own, set android:exported to "true". -->
<receiver
    android:name=".MyBroadcastReceiverWithPermission"
    android:permission="android.permission.ACCESS_COARSE_LOCATION"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
    </intent-filter>
</receiver>

或者,接收应用具有上下文注册的接收器,如下所示:

Kotlin

ContextCompat.registerReceiver(
    context, myBroadcastReceiver, filter,
    android.Manifest.permission.ACCESS_COARSE_LOCATION,
    null, // scheduler that defines thread, null means run on main thread
    receiverFlags
)

Java

ContextCompat.registerReceiver(
        context, myBroadcastReceiver, filter,
        android.Manifest.permission.ACCESS_COARSE_LOCATION,
        null, // scheduler that defines thread, null means run on main thread
        receiverFlags
);

然后,为了能够向这些接收器发送广播,发送应用必须按如下方式请求权限:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

避免在同一进程内进行广播

广播是一种进程间通信 (IPC) 机制,用于在不同应用之间或在系统与应用之间发送消息。发送自广播(即所有接收器都在发送广播的同一进程中运行的广播)非常低效,会产生不必要的系统开销,因此强烈建议不要这样做。

应用向自身发送广播的两种常见情况包括:

  • 在同一进程中的组件之间进行通信:例如,在 activity、fragment、服务或后台线程之间传递事件或数据。请使用标准进程内通信机制(例如观察者模式或响应式流)来代替发送广播:

    • Kotlin Flow(SharedFlow 和 StateFlow):一种现代的 Kotlin 惯用解决方案,用于在应用中的协程和组件之间发出和观察事件流或状态更新。
    • 共享 ViewModel:有助于在同一 activity 内的不同界面组件(例如 fragment 或可组合项)之间共享数据和事件。
    • 回调和监听器:在组件之间直接传递或注册到中央代码库或控制器的标准接口回调或函数引用。
  • 处理系统事件、作业或闹钟:例如,接收预定作业、闹钟或系统回调,然后发送广播以触发实际工作。您可以在该作业(例如 JobService 或 WorkManager worker)、闹钟处理程序或系统回调组件中直接完成工作,而不是发送广播,或者直接委托给应用的业务逻辑类。

在搭载 Android 17 QPR2 或更高版本的设备上,系统会通过将自广播返回到发送进程来更高效地传送自广播,该进程会将自广播传送到主线程上的自有接收器。发送自广播不会提高进程的重要性,也不会阻止缓存的进程被冻结,因此不要依赖自广播来保持应用运行或在后台执行工作。即使经过这种优化,自广播的效率也低于进程内通信机制,因此请改用这些替代方案。

如需详细了解如何设计应用组件之间的通信,请参阅应用架构指南。

安全注意事项

以下是发送和接收广播时的一些安全注意事项:

  • 如果许多应用已注册在清单中接收同一广播,则可能会导致系统启动大量应用,从而对设备性能和用户体验产生重大影响。为避免此问题,建议使用上下文注册,而不是清单声明。有时,Android 系统本身会强制使用通过上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播仅会传递给通过上下文注册的接收器。

  • 请勿使用隐式 intent 广播敏感信息。任何应用都可以读取该信息,前提是该应用已注册接收广播。 您可以通过以下三种方式控制哪些应用可以接收您的广播:

    • 您可以在发送广播时指定权限。
    • 在 Android 4.0(API 级别 14)及更高版本中,您可以在发送广播时使用 setPackage(String) 指定 软件包。系统会将广播限制为与软件包匹配的应用集。
  • 注册接收器后,任何应用都可以向您应用的接收器发送可能具有恶意性质的广播。您可以通过多种方式限制应用接收的广播:

    • 您可以在注册广播接收器时指定权限。
    • 对于清单声明的接收器,您可以在清单中将 android:exported 属性设置为“false”。接收器不接收来自应用外部来源的广播。
  • 广播操作的命名空间是全局性的。确保操作名称和其他字符串是在您拥有的命名空间中编写的。否则,您可能会无意中与其他应用发生冲突。

  • 由于接收器的 onReceive(Context, Intent) 方法在主线程上运行,因此应快速执行并返回。如果您需要执行长时间运行的工作,请谨慎生成线程或启动后台服务,因为系统可能会在 onReceive() 返回后终止整个进程。如需了解详情,请参阅对进程状态的影响。为了执行长时间运行的工作,我们建议:

    • 在接收器的 onReceive() 方法中调用 goAsync(),并将 BroadcastReceiver.PendingResult 传递给后台线程。这样一来,从 onReceive() 返回后,广播会保持有效状态。不过,即使采用这种方法,系统也希望您能非常快速地完成广播(在 10 秒内)。不过,它允许您将工作移至另一个线程,以避免主线程出现故障。
    • 使用 JobScheduler 调度作业。如需了解详情,请参阅智能作业调度。
  • 请勿从广播接收器启动 activity,因为这样会给用户带来不流畅的体验;尤其是在有多个接收器的情况下。请考虑改为显示通知。