前言
Android 系统引入了一套强大的广播消息机制。它不仅可用于接收系统级别的消息通知,还可用于应用与系统之间、以及不同应用之间的消息传递。
尽管广播可用于应用内不同组件之间 的通信,但需要通过系统服务进行中转,效率较低。推荐使用 ViewModel
配合 LiveData
或 StateFlow
来完成。
广播机制简介
在 Android 中,每个应用程序都可以注册广播接收器(BroadcastReceiver
),从而"订阅"感兴趣的消息。广播可能来自于系统,也可能来自于其他应用,每当"发布"一个广播时,所有订阅了该广播的接收器都会进行响应。
Android 提供了一套完整的 API 来让应用程序发送和接收广播,发送广播其实是借助了 Intent 对象来承载信息,而接收广播则需要我们实现 BroadcastReceiver。
Android 中的广播主要分为两种类型:
-
标准广播(Normal Broadcasts) :它是一种异步执行的广播,当广播消息发出后,所有匹配的 BroadcastReceiver 都会几乎同时收到这条广播消息,它们之间没有先后顺序可言。这种方式的效率很高,但中途也无法进行截断。
标准广播的工作流程图:
-
有序广播(Ordered Broadcasts) :它则是一种同步执行 的广播,在广播发出之后,同一时刻,只会有一个 BroadcastReceiver 能够收到这条广播消息。系统会按照接收器设置的优先级(priority)从高到低依次传递广播。每一个 BroadcastReceiver 接收器都可以选择通过
abortBroadcast()
方法截断当前正在传递的广播,这样其后的 BroadcastReceiver 就无法收到广播消息,并且接收器还能修改广播。有序广播的工作流程图:
我们先从接收系统广播开始,看看 BroadcastReceiver
的用法。
接收系统广播
Android 系统在运行时就会发出各种广播,比如开机完成的广播、系统时间改变的广播、电量改变的广播等。我们可以监听这些广播,得到各种系统的状态信息,并且进行逻辑处理。
动态注册:监听时间变化
注册 BroadcastReceiver
的方式有两种,分别是通过代码注册和在 AndroidManifest.xml
清单文件中注册。前者被称为动态注册,后者被称为静态注册。
我们现在通过动态注册的方式来完成对系统时间变化的监听。
首先创建一个名为 BroadcastTest
的 Empty Views Activity
项目。创建一个类 TimeChangeReceiver
,继承自 BroadcastReceiver
,并重写 onReceive()
方法。这样每当收到广播时,就会执行该方法内部的逻辑。
代码如下:
kotlin
class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// 确认收到的广播 Action 是 Intent.ACTION_TIME_TICK
if (intent.action == Intent.ACTION_TIME_TICK) {
Toast.makeText(context, "时间发生了变化", Toast.LENGTH_SHORT).show()
}
}
}
在这里,收到了广播消息后,我们仅是弹出了一段文字提示。
然后在 MainActivity
中进行动态注册和注销:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val timeChangeReceiver = TimeChangeReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val intentFilter = IntentFilter()
// 添加要监听的广播 Action
intentFilter.addAction(Intent.ACTION_TIME_TICK)
// 注册广播接收器
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// 在Android 13 (API 33)以上,必须指定接收器是否对外部应用可见
registerReceiver(timeChangeReceiver, intentFilter, RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(timeChangeReceiver, intentFilter)
}
}
override fun onDestroy() {
super.onDestroy()
// 取消注册
unregisterReceiver(timeChangeReceiver)
}
}
我们在 onCreate()
方法中,首先创建了 IntentFilter
的实例,并且调用了它的 addAction()
方法指定了我们要监听哪条广播。然后创建了 TimeChangeReceiver
的实例,调用 registerReceiver()
方法对广播接收器进行注册,这样就实现了监听系统时间变化的功能。
最后我们在 onDestroy()
方法中通过调用 unregisterReceiver()
方法取消注册了之前的 TimeChangeReceiver
。
注意:动态注册的 BroadcastReceiver 一定要取消注册,否则会造成内存泄漏。
运行程序,最多等待一分钟,你就可以看到弹出的提示。如图所示:
这就是动态注册 BroadcastReceiver 的基本用法,接收其他系统广播的方式是一样的。
完整的广播列表,请查看你本地的 <Android SDK 路径>\platforms\android-<api 版本号>\data\broadcast_actions.txt 文件,如我的是
D:\Develop\SDK\platforms\android-34\data\broadcast_actions.txt
。
静态注册:实现开机启动
动态注册虽然可以自由控制 BroadcastReceiver 的注册和注销,但它只能在程序运行后,才能接收广播。有时,你需要在程序未启动的情况下也能接收广播消息,如监听系统启动,这就需要使用静态注册了。
按理来说,任何可以动态注册方式监听到的广播,以静态注册的方式也能监听到,但 Android 8.0 (API 26)以后,为了降低系统功耗、提升性能,所有的隐式广播都不允许使用静态注册的方式来接收了。隐式广播指没有具体指定发送给哪个应用的广播,这样看来,其实大部分的系统广播都属于隐式广播,但有少部分特殊的系统广播仍然允许以静态注册的方式来监听。
详见:隐式广播例外情况
其中 ACTION_BOOT_COMPLETED
是开机完成的广播,我们来完成监听系统开机完成。
我们通过 Android Studio 提供的快捷方式来创建 BroadcastReceiver
:右键包名,点击 New->Other->Broadcasr Receiver,从而来到创建窗口。
其中新建类的类名为 BootCompleteReceiver
,Exported 选项表示是否允许接收外部程序的广播,Enabled 选项表示是否启用当前的 BroadcastReceiver,这两个选项我们都勾选上。
创建完成后,我们修改 onReceive()
方法,在内部发送一条通知,你可以先不用管发送通知的具体细节。
kotlin
class BootCompleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// 发送通知
showBootNotification(context)
}
}
private fun showBootNotification(context: Context) {
val channelId = "boot_channel"
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// 创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Boot Complete Notifications",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
// 创建通知
val notification = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("应用已准备就绪")
.setContentText("成功接收到开机广播。")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
// 发送通知
notificationManager.notify(1, notification)
}
}
随后需要在 AndroidManifest.xml
清单文件中注册,不过由于我们是通过 Android Studio 提供的快捷方式创建的 BroadcastReceiver,所以它会帮我们自动注册,如下所示:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
...>
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
...
</application>
</manifest>
不过现在我们还没有"订阅"任何广播消息,我们通过 <intent-filter>
标签,来指定其所接收 action。
xml
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Android 规定:对用户来说敏感的操作,应用需要在清单文件中进行权限声明。就比如监听系统开机的广播,我们就需要声明,如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--声明监听系统开机广播的权限-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
...>
...
</application>
</manifest>
并且从 Android 13 (API 33) 开始,发送通知需要 POST_NOTIFICATIONS
权限,你也需要进行声明:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--声明发送通知的权限-->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
</manifest>
最后你需要在虚拟机中启用该应用的通知权限:
重新启动程序,并且重启虚拟机,然后在开机完成的时候你可以看到:
注意:不要在 onReceive()
方法中执行耗时操作,否则会阻塞主线程,导致程序出现无响应错误(ANR)。
那么可以在 onReceive()
方法中开启子线程去执行耗时操作吗?
onReceive()
方法的生命周期特别短,方法返回后,其所在的进程随时可能被回收,导致开启的子线程可能会在任务完成前被终止,所以应该使用 WorkManager
将耗时任务交给系统调度,具体实现方式就不再详细说了。