广播概览

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)或更高版本的设备上,系统不会在 Wi-Fi 广播中包含 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 之外,以使其变为无状态:

@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 过滤器的 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 或更高版本的设备上,系统 会更高效地传递自广播,方法是将自广播返回给 发送进程,然后由发送进程在主线程上将其传递给自己的接收器。 发送自广播不会提高进程的重要性,也不会阻止缓存的进程被冻结,因此请勿依赖自广播来保持应用运行或在后台执行工作。请改为根据 后台任务概览中所述,为您的任务选择 适当的 API。即使进行了这种优化,自广播的效率也低于进程内通信机制,因此请改用这些替代方案。

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

安全注意事项

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

  • 如果许多应用都在其清单中注册接收同一广播,则可能会导致系统启动大量应用,从而对设备性能和用户体验产生重大影响。为避免这种情况,请优先使用上下文注册,而不是清单声明。有时,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,因为用户体验会很糟糕;尤其是在有多个接收器的情况下。请改为考虑 显示通知。