Android - Broadcast Receiver
Android - BroadcastReceiver(更新)
Section titled “Android - BroadcastReceiver(更新)”BroadcastReceiver 是 Android 组件,允许您注册接收系统级或应用特定的广播消息(Intent)。它们充当监听器,监听来自系统(例如,启动完成、电池电量低)或其他应用(例如,数据下载完成)的事件。当注册的事件发生时,Android 系统会通知相应的 BroadcastReceiver,然后它可以执行相应的操作。
使 BroadcastReceiver 工作主要有两种方式:
- 静态注册(Manifest 声明): 接收器在应用的
AndroidManifest.xml中声明。系统可以启动您的应用(如果尚未运行)来传递广播。这种方法有明显的限制,特别是在 Android 8.0 (API level 26) 及更高版本上,对隐式广播限制较多,以节省系统资源。 - 动态注册(Context 注册): 接收器在运行时使用
Context.registerReceiver()注册。它仅在注册组件(例如 Activity 或 Service)存活期间处于活动状态。对于仅在应用处于前台或特定组件处于活动状态时才相关的广播,通常首选此方法。
如果您要广播自定义 Intent,还需要从您的应用中创建和发送这些 Intent。
创建 BroadcastReceiver
Section titled “创建 BroadcastReceiver”BroadcastReceiver 实现为 android.content.BroadcastReceiver 的子类。您必须重写 onReceive() 方法,在该方法中,每个广播消息都作为 Intent 对象接收。
这是一个 Kotlin 的简单示例:
package com.example.myapp
import android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.widget.Toast
class MyCustomReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // 检查 context 和 intent 是否为空,并验证 intent 的 action 是个好习惯。 if (context != null && intent != null) { val action = intent.action if ("com.example.myapp.CUSTOM_INTENT" == action) { Toast.makeText(context, "Custom Intent Detected!", Toast.LENGTH_LONG).show() // 重要:onReceive() 在主线程中运行。 // 对于长时间运行的任务,启动 Service 或使用 WorkManager。 // 请参阅:https://developer.android.com/guide/components/broadcasts#effects-process-state } } }}onReceive() 的重要注意事项:
- 执行时间:
onReceive()方法在主 UI 线程上运行。它应该快速执行(通常在 10 秒内)。对于长时间运行的操作,请将工作委托给JobIntentService、WorkManager或启动前台服务。 - 后台执行限制: 定位到 Android 8.0 (API 26) 及更高版本的应用对后台服务有限制。如果广播接收器触发后台工作,请确保其符合这些限制。例如,如果工作在接收器允许的生命周期内卸载到另一个线程,则可以使用
goAsync()允许更多时间进行处理。
注册 BroadcastReceiver
Section titled “注册 BroadcastReceiver”1. 静态注册(Manifest 声明)
Section titled “1. 静态注册(Manifest 声明)”要在 Manifest 中注册接收器,请在 AndroidManifest.xml 的 <application> 标签内添加一个 <receiver> 元素。这适用于系统广播,例如 android.intent.action.BOOT_COMPLETED(但请注意,在 Android N 之前,如果应用安装在外部存储上且从未启动过,则不会收到此广播)。
<!-- 在 AndroidManifest.xml 中 --><application ...> <receiver android:name=".MyBootReceiver" android:enabled="true" android:exported="true"> <!-- 如果需要接收来自应用外部的广播,请将 exported 设置为 true --> <!-- 对于 BOOT_COMPLETED,exported 通常应为 true --> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
<receiver android:name=".MyCustomReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.myapp.CUSTOM_INTENT" /> </intent-filter> </receiver></application>对应的 MyBootReceiver.kt:
package com.example.myapp
import android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.util.Log
class MyBootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_BOOT_COMPLETED == intent.action) { Log.d("MyBootReceiver", "Boot completed event received.") // 执行启动时所需的操作,例如调度 alarm 或 job。 } }}Manifest 注册限制(Android 8.0+):
对于定位到 Android 8.0 (API 26) 或更高版本的应用,系统对大多数隐式广播(未专门针对您的应用包的广播)的 Manifest 声明接收器施加了限制。有一些例外情况(例如 BOOT_COMPLETED、ACTION_LOCKED_BOOT_COMPLETED)。对于其他隐式广播,您应使用 Context 注册的接收器。显式广播(Intent 指定您的应用组件)仍会传递给 Manifest 声明的接收器。
2. 动态注册(Context 注册)
Section titled “2. 动态注册(Context 注册)”使用 Context.registerReceiver() 动态注册接收器。这通常在 Activity 的 onResume() 或 Service 的 onCreate() 中完成。至关重要的是,当组件不再活动时(例如,在 Activity 的 onPause() 中或 Service 的 onDestroy() 中),您必须使用 Context.unregisterReceiver() 注销它,以防止内存泄漏和不必要的系统负载。
// 在 Activity 中(Kotlin)private var myNetworkReceiver: BroadcastReceiver? = null
override fun onResume() { super.onResume() myNetworkReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // 处理网络变化,例如 ConnectivityManager.CONNECTIVITY_ACTION // 注意:CONNECTIVITY_ACTION 在 Android N+ 上也有限制 // 使用 NetworkCallback API 进行更可靠的网络监控。 Toast.makeText(context, "Network state changed", Toast.LENGTH_SHORT).show() } } val filter = IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION) // 对于仅应由您的应用访问的接收器,您可以指定权限 // 或者如果是在 Manifest 中声明的,可以在 Manifest 中设置 android:exported="false"。 // 对于 Context 注册的接收器,使用 registerReceiver 的变体控制可见性。 // 如果接收器监听不需要权限的系统广播,要使其对您的应用私有: // 不需要特殊标志,因为它默认不会导出。 // 如果注册的广播需要权限才能发送,请指定该权限: // registerReceiver(myNetworkReceiver, filter, "com.example.SOME_PERMISSION", null) // 对于现代 Android,如果可用,请使用带有 `exported` 标志的版本: // 对于 Android 13 (API 33) 及更高版本,您必须指定接收器是否被导出。 // ContextCompat.registerReceiver(this, myNetworkReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED) // 或者如果它需要被导出: // ContextCompat.registerReceiver(this, myNetworkReceiver, filter, ContextCompat.RECEIVER_EXPORTED) // 对于常见的系统广播(如 CONNECTIVITY_ACTION)的简单用法: registerReceiver(myNetworkReceiver, filter)}
override fun onPause() { super.onPause() myNetworkReceiver?.let { unregisterReceiver(it) myNetworkReceiver = null }}对于 Android 13 (API 33) 及更高版本,为非受保护广播注册接收器时,必须使用 Context.RECEIVER_EXPORTED 或 Context.RECEIVER_NOT_EXPORTED 等标志指定其是否被导出。使用 ContextCompat.registerReceiver 以实现兼容性。
可以在 Intent.java 或官方文档中找到常见的系统广播 Action 列表:https://developer.android.com/reference/android/content/Intent
| 序号 | 现代事件监听方法及描述 |
|---|---|
| 1 | android.intent.action.BATTERY_CHANGED包含电池状态的粘性广播。仍可使用,但对于精确的电池电量变化,如果绝对需要,请考虑使用前台服务或 WorkManager 进行定期检查,这是由于后台限制造成的。 |
| 2 | android.intent.action.BOOT_COMPLETED系统启动后广播一次。对于调度任务很有用。需要 RECEIVE_BOOT_COMPLETED 权限。 |
| 3 | android.net.conn.CONNECTIVITY_CHANGE (原 android.net.ConnectivityManager.CONNECTIVITY_ACTION)指示网络连接变化。对于定位到 N (API 24)+ 的应用,此广播的 Manifest 注册受到限制。使用 ConnectivityManager.registerNetworkCallback() 进行可靠的网络监控。 |
| 4 | android.intent.action.AIRPLANE_MODE (原 android.intent.action.AIRPLANE_MODE_CHANGED)指示飞行模式已切换。Manifest 注册的接收器也受后台限制。 |
| 5 | android.intent.action.SCREEN_ON / android.intent.action.SCREEN_OFF用户屏幕活动。通常需要 Context 注册。这些是受保护的 Intent。 |
| 6 | android.intent.action.MY_PACKAGE_REPLACED应用更新时发送给您的应用。对于更新后重新调度 Job 或 Alarm 很有用。 |
对于您打算使用的特定广播 Action,请务必查阅官方文档,因为不同 Android 版本之间的行为和限制可能会发生变化。
广播自定义 Intent
Section titled “广播自定义 Intent”您的应用可以生成和发送自己的自定义 Intent。这对于应用内部的组件间通信甚至不同应用之间的通信(如果设计允许且权限允许)很有用。
要发送普通广播,使用 sendBroadcast(Intent)。对于有序广播,使用 sendOrderedBroadcast(...)。
// 在 Activity 或 Service 中(Kotlin)fun broadcastCustomIntent() { val intent = Intent() // Action 应唯一,通常使用您的包名作为前缀。 intent.action = "com.example.myapp.CUSTOM_INTENT" intent.putExtra("data", "Some important value") // 可选:添加数据 // 将广播限制到应用内的组件: intent.setPackage(packageName) // 如果是内部广播,出于安全考虑强烈推荐 sendBroadcast(intent)
// 如果接收器需要权限才能接收此广播: // sendBroadcast(intent, "com.example.myapp.permission.RECEIVE_CUSTOM_INTENT")}LocalBroadcastManager(已弃用但值得了解的概念):
对于仅在您的应用内部发送广播,以前推荐使用 LocalBroadcastManager(来自 androidx.localbroadcastmanager:localbroadcastmanager)。它更高效、更安全,因为 Intent 不会离开您的应用。但是,LocalBroadcastManager 现已弃用。官方建议对于应用内通信使用其他观察者模式,如 LiveData、Flow(Kotlin 协程)或事件总线库,因为它们更具生命周期感知能力且更易于测试。如果您需要直接替代应用内广播行为且无法重构,仍可以使用全局 sendBroadcast 并结合 Intent.setPackage(packageName) 将其限制在您的应用内。
关于粘性广播的注意事项: sendStickyBroadcast(Intent) 方法自 API level 21 起已弃用。粘性广播由于安全和性能问题而存在问题。请避免使用它们。如果您需要类似的行为,请考虑使用其他组件可以绑定的前台服务,或者持久化状态并在组件激活时查询它。
示例:自定义 Intent 广播和接收
Section titled “示例:自定义 Intent 广播和接收”此示例演示了从 Activity 广播自定义 Intent,并使用动态注册的 BroadcastReceiver 接收它。
-
创建 BroadcastReceiver(
MyCustomReceiver.kt- 前面已展示): 确保它监听"com.example.myapp.CUSTOM_INTENT"。 -
修改
MainActivity.kt以注册接收器并发送广播:
package com.example.myapp
import androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.content.Intentimport android.content.IntentFilterimport android.widget.Buttonimport androidx.core.content.ContextCompat // 用于 registerReceiver 兼容性
class MainActivity : AppCompatActivity() {
private var customReceiver: MyCustomReceiver? = null private val CUSTOM_INTENT_ACTION = "com.example.myapp.CUSTOM_INTENT"
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 假设有一个包含按钮的布局
val sendBroadcastButton: Button = findViewById(R.id.sendBroadcastButton) sendBroadcastButton.setOnClickListener { broadcastIntent() } }
override fun onResume() { super.onResume() customReceiver = MyCustomReceiver() val intentFilter = IntentFilter(CUSTOM_INTENT_ACTION) // 对于 Android 13+ 目标,指定 exported 标志。对于旧版本使用 ContextCompat。 ContextCompat.registerReceiver(this, customReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED) }
override fun onPause() { super.onPause() customReceiver?.let { unregisterReceiver(it) customReceiver = null } }
private fun broadcastIntent() { val intent = Intent(CUSTOM_INTENT_ACTION) intent.putExtra("message", "Hello from MainActivity!") // 出于安全考虑,限制为本应用的包名 intent.setPackage(packageName) sendBroadcast(intent) }}- 布局文件(
res/layout/activity_main.xml): 添加一个按钮。
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center">
<Button android:id="@+id/sendBroadcastButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Custom Broadcast" />
</LinearLayout>- 在此动态示例中,
MyCustomReceiver不需要 Manifest 注册。 如果要对自定义 Intent 使用 Manifest 注册,则需要将其添加到AndroidManifest.xml中,如前所示,如果仅用于应用内部使用,则设置android:exported="false"。
运行应用。点击按钮将发送自定义广播,并且 MyCustomReceiver 将显示一个 Toast 消息。这展示了使用现代实践在应用内进行自定义 Intent 广播和接收的基本流程。
对于更复杂的场景或应用间通信,请仔细考虑权限、Intent 过滤器和安全隐患(例如,用于您自己应用之间可信通信的基于签名的权限)。