广播概览

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 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 保持活跃状态,您就会收到广播。如果您在应用上下文中注册,只要应用在运行,您就会收到广播。

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

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

    Groovy

    dependencies {
        def core_version = "1.13.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.0.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-alpha02"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.13.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.0.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-alpha02")
    }
    
  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)。当您不再需要接收器或上下文不再有效时,请务必注销接收器。

取消注册广播接收器

在广播接收器注册期间,它会保留对您用于注册它的上下文的引用。如果接收器的注册范围超出上下文生命周期范围,则可能会导致泄漏。例如,如果您在 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" />

安全注意事项

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

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