BroadcastReceiver 简介
广播用于发送通知消息,应用程序可以选择接收自己感兴趣的广播,广播的接收方式为注册 BroadcastReceiver,然后在其 onReceive 方法中处理接收到的广播。
广播分为标准广播
和有序广播
。两者的区别在于:标准广播发出后,所有的 BroadcastReceiver 会同时收到,而有序广播会按照 BroadcastReceiver 的优先级或注册顺序依次接收、依次处理,并且前面的 BroadcastReceiver 可以中断广播的继续传播。
广播还可以按照另一个维度划分为显式广播
和隐式广播
,隐式广播指的是没有具体指定发送给哪个应用程序的广播。系统广播大多数都是隐式广播。
BroadcastReceiver 有两种注册方式:动态注册
和静态注册
。动态注册是指在代码中注册 BroadcastReceiver,静态注册指的是在 AndroidManifest 中注册 BroadcastReceiver。动态注册的 BroadcastReceiver 只有在程序运行时,代码中调用 registerReceiver 注册了之后才能收到广播,而静态注册的广播即使程序没有启动也能接收到广播。
由于静态注册经常被滥用,所以自 Android 8.0 以后,所有的隐式广播都不允许使用静态注册的方式来接收了。大多数系统广播属于隐式广播,只有少数特殊的系统广播仍然允许使用静态注册的方式来接收。这些特殊的系统广播在这里可以找到:https://developer.android.google.cn/guide/components/broadcast-exceptions.html
一、动态注册
系统每隔一分钟就会发出一条 Intent.ACTION_TIME_TICK
广播,这个广播的意思是系统时间发生了变化。接下来我们就用动态注册 BroadcastReceiver 的方式来接收它。
新建 TimeChangeBroadcastReceiver:
class TimeChangeBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("~~~", "Time has changed")
}
}
MainActivity 中动态注册:
class MainActivity : AppCompatActivity() {
private val receiver by lazy { TimeChangeBroadcastReceiver() }
private val intentFilter by lazy {
IntentFilter().apply {
addAction(Intent.ACTION_TIME_TICK)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
registerReceiver(receiver, intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
}
使用registerReceiver
和 unregisterReceiver
就完成了注册和取消注册,注册时传入的 intentFilter
用来指定接收广播的类型。最多等待一分钟就能在控制台看到 Log:
~~~: Time has changed
监听其他系统广播的做法也是类似的,完整的系统广播列表可以在 Intent 类中查看,也可以在 SDK 中查看,路径为:
SDK 路径/platforms/任意 android api 版本/data/broadcast_actions.txt
二、静态注册
在上文的特殊系统广播列表中,可以看到 Intent.ACTION_BOOT_COMPLETED
是可以静态注册的,这个广播是指系统开机完成。接下来我们就用静态注册 BroadcastReceiver 的方式来接收它。
新建 BootCompleteBroadcastReceiver:
class BootCompleteBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("~~~", "boot complete")
}
}
在 AndroidManifest 中注册这个 Receiver:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application ...>
...
<receiver android:name=".BootCompleteBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
需要注意的是,接收此静态广播必须声明 RECEIVE_BOOT_COMPLETED
权限,这是为了让用户知道我们需要接收开机完成的广播,Android 希望所有应用都做到对用户透明。安装 app 后重启手机,待系统系统完成后,就可以在控制台看到 Log:
~~~: boot complete
三、发送自定义广播
接下来我们来发送一条自定义的广播,并接收它。
先新建一个类来接收我们的自定义广播,新建 MyBroadcastReceiver:
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("~~~", "receive my broadcast")
}
}
静态注册一下:
<application ...>
...
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
</application>
然后我们可以在任何地方发送此广播进行测试,比如 MainActivity 启动时发送:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
setPackage(packageName)
})
}
}
此时打开 MainActivity,就可以在控制台看到 Log:
~~~: receive my broadcast
注意这里我们使用了 setPackage
方法,传入了我们这个应用的包名,表示这个广播指定发送给我们的这个应用程序,只有这样它才是一个显式广播。上文已经说过,Android 8.0 以后,隐式广播是无法通过静态注册接收到的。
本例也可以使用动态注册的方式,只需删除 AndroidManifest 中静态注册的代码,再修改 MainActivity 如下:
class MainActivity : AppCompatActivity() {
private val receiver by lazy { MyBroadcastReceiver() }
private val intentFilter by lazy {
IntentFilter().apply {
addAction("com.wkxjc.myapplication.MY_BROADCAST")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
registerReceiver(receiver, intentFilter)
sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST"))
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
}
这样就完成了动态注册,动态注册时我们就不需要使用 setPackage
方法了,运行效果和静态注册一样。
四、有序广播
intentFilter 可以设置 priority
属性,表示 BroadcastReceiver 的优先级。发送有序广播时,优先级高的 BroadcastReceiver 会先接收到,处理后再传递给优先级第二高的 BroadcastReceiver,以此类推。priority 默认是 0,官方规定 priority 的范围应该在 (-1000, 1000) 区间,因为系统会使用优先级 1000 这个值,使系统的一些广播被最高优先级接收,我们不应该打破这个规定。但编译器不会检查 priority 的值是否在区间内,所以这个规定只是一个契约。
静态注册的 BroadcastReceiver 通过添加 android:priority 设置 priority 属性,例如:
<receiver android:name=".HighPriorityBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
动态注册的 BroadcastReceiver 通过给 intentFilter 设置,例如:
private val intentFilter by lazy {
IntentFilter().apply {
addAction("com.wkxjc.myapplication.MY_BROADCAST")
priority = 100
}
}
如果两个 BroadcastReceiver 优先级相同,则先注册的 BroadcastReceiver 会先接收到。对于静态注册,就受 AndroidManifest 中注册的顺序影响,对于动态注册,就受代码中调用 registerReceiver 的顺序影响。
有序广播的 BroadcastReceiver 中,可以调用 abortBroadcast 函数中断此广播。发送有序广播的方式也很简单。调用 sendOrderedBroadcast(yourIntent, null)
即可,这个函数的第二个参数是与权限有关的,我们暂时用不到,传递 null 即可。
接下来我们来测试验证一下。
新建 HighPriorityBroadcastReceiver 类:
class HighPriorityBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("high", "receive")
abortBroadcast()
}
}
在这个类中,我们收到广播后,打印日志,然后将广播中断。
新建 LowPriorityBroadcastReceiver 类:
class LowPriorityBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("low", "receive")
}
}
这个类中仅仅打印一行日志。
然后静态注册一下这两个 BroadcastReceiver :
<application ...>
...
<receiver android:name=".LowPriorityBroadcastReceiver">
<intent-filter android:priority="0">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
<receiver android:name=".HighPriorityBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
</intent-filter>
</receiver>
</application>
我们将两个 BroadcastReceiver 的 priority 分别设置成了 100 和 0 。这里我故意让 LowPriorityBroadcastReceiver 先注册,以验证 priority 真的能改变接收次序。
在 MainActivity 中发送显式广播:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendOrderedBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
setPackage(packageName)
}, null)
}
}
运行程序,控制台 Log 如下:
high: receive
如果将 AndroidManifest 中,两个 BroadcastReceiver 的 priority 属性都去掉,则控制台会输出:
low: receive
high: receive
这是因为 LowPriorityBroadcastReceiver 是先注册的,并且 LowPriorityBroadcastReceiver 中没有中断广播继续传播。所以我们验证了 priority 确实能改变 BroadcastReceiver 的接收次序。并且 abortBroadcast 确实能中断有序广播的传播。
如果需要在广播中携带数据,只需在发送广播时将数据传到 intent 中,再从 BroadcastReceiver 的 onReceive 函数中的 intent 参数中取出即可。