广播概览

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_PRIORITYSYSTEM_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广播。无法在清单中声明接收器。

接收广播

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

上下文注册的接收器

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

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

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

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

如果系统检测到自广播,可能会尝试通过在进程内重定向来优化传送。不过,这仍然不如前面介绍的进程内通信替代方案高效,您应该改用这些替代方案。

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

安全注意事项

以下是有关收发广播的一些安全注意事项:

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