广播概览

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

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

一般来说,广播可用作跨应用且在正常用户流之外的消息传递系统。但是,您必须小心,不要滥用响应广播并在后台运行可能导致系统性能缓慢的任务的机会。

关于系统广播

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

Intent 对象封装了广播消息。action 字符串标识发生的事件,例如 android.intent.action.AIRPLANE_MODE。Intent 还可能包含捆绑在其额外字段中的附加信息。例如,飞行模式 Intent 包含一个布尔额外信息,指示飞行模式是否开启。

有关如何读取 Intent 以及从 Intent 获取 action 字符串的详细信息,请参阅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)或更高版本的设备上,系统将不会在 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 广播。在清单中声明接收器将不起作用。

接收广播

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

上下文注册的接收器

上下文注册的接收器只要其注册上下文有效,就会接收广播。这通常发生在调用 registerReceiverunregisterReceiver 之间。当系统销毁相应的上下文时,注册上下文也会变得无效。例如,如果您在 Activity 上下文中注册,则只要该 Activity 保持活跃,您就会接收广播。如果您使用 Application 上下文注册,则只要应用运行,您就会接收广播。

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

  1. 在您应用的模块级构建文件中,包含 1.9.0 或更高版本的 AndroidX Core 库

    Groovy

    dependencies {
        def core_version = "1.16.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-beta02"
    }

    Kotlin

    dependencies {
        val core_version = "1.16.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-beta02")
    }
  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
}

清单声明的接收器

注意:如果您的应用面向 API 级别 26 或更高版本,则不能使用清单为隐式广播声明接收器,但少数不受该限制约束的隐式广播除外。隐式广播是指未明确指定您的应用的广播。在大多数情况下,您可以使用调度作业代替。

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

  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" />

安全注意事项

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

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