一、认识
广播接收者BroadcastReceiver也是Android的四大组件之一,和Service一样,它也是没有UI视图的,无感知运行在后台。主要作用于接收系统发出来的比如网络变化、应用卸载安装、电池电量变化等等广播,以及应用间组件解耦和通信。
广播接收者有两种注册方式:静态注册、动态注册。静态注册就是在manifest中注册的广播,它不受其他组件的影响,适用于需要随时监听广播,缺点就是占用内存和耗电。动态注册就是不需要在manifest中注册的广播,而是在代码中注册,就会和当前组件关联,当开启它的组件销毁时,BroadcastReceiver也会被销毁。
二、基本使用
BroadcastReceiver它是一个抽象类,所以我们无法直接创建BroadcastReceiver的实例对象,需要自己新建一个类继承BroadcastReceiver,并且重写onReceive这个抽象方法。我们新建两个广播接收者的类,一个表示静态注册:MyStaticBroadcastReceiver;一个表示动态注册:MyDynamicBroadcastReceiver。
- 静态注册
class MyStaticBroadcastReceiver : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("静态接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
}
}
}
静态注册的BroadcastReceiver,只需要在manifest中配置receiver节点,我们需要对自己的接收者进行过滤,判断是否是自己使用的相关广播消息。
<receiver android:name="com.test.broadcast.use.MyStaticBroadcastReceiver">
<intent-filter>
<action android:name="com.test.broadcast.use.helloworld"/>
</intent-filter>
</receiver>
在MainActivity中放置一个按钮,用来发送一个广播,并传递一个参数,当能够接收到此广播的接收者收到广播后,就可以获取到当中的参数,布局文件就不贴了。
class MainActivity : AppCompatActivity() {
private var sendBroadcastBtn: Button? = null
companion object{
val Key = "key"
val MyAction = "com.test.broadcast.use.helloworld"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendBroadcastBtn = findViewById(R.id.send_broadcast_btn)
sendBroadcastBtn?.setOnClickListener {
val myIntent = Intent(MyAction)
myIntent.putExtra(Key,"hello world")
sendBroadcast(myIntent)
}
}
}
然后运行程序,点击按钮后,可以得到以下输出。可以看到成功接收到了广播消息

- 动态注册
class MyDynamicBroadcastReceiver : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("动态接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
}
}
}
可以看到,广播接收者的代码是一样的,区别在于动态注册的接收者无需在manifest中进行注册,需要在代码中进行动态注册。我们在MainActivity的onCreate方法中添加一个initDynamic()方法调用,并且在onStop()方法中调用unregisterReceiver对广播进行解注册,毕竟广播接收者也是Android的四大组件之一,也是很消耗内存资源的,如果不及时解注册,会造成内存泄漏。
private val myDynamicBroadcastReceiver = MyDynamicBroadcastReceiver()
private fun initDynamic(){
val intentFilter = IntentFilter()
intentFilter.addAction(MyAction)
registerReceiver(myDynamicBroadcastReceiver,intentFilter)
}
override fun onStop() {
super.onStop()
unregisterReceiver(myDynamicBroadcastReceiver)
}
运行程序,点击发送广播按钮,得到以下输出。可以看到,成功收到广播消息

- 有序广播
以上不管是动态注册还是静态注册,发送广播都是使用的sendBroadcast方法,这个方法是发送的无序广播。如果需要让广播接收者按序接收消息,则需要发送有序广播,通过使用sendOrderedBroadcast方法以及配合priority属性。如果是静态注册的,需要在intent-filter中添加android:priority属性;如果是动态注册的,需要调用intentFilter对象的intentFilter.setPriority()方法
这里,我们再创建一个静态广播接收者SecondBroadcastReceiver,其中的代码和MyStaticBroadcastReceiver完全一样,就不再贴出来了。然后我们设置SecondBroadcastReceiver的优先级值为100,MyStaticBroadcastReceiver为102,MyDynamicBroadcastReceiver为101。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.broadcast.use">
...
<receiver android:name="com.test.broadcast.use.MyStaticBroadcastReceiver">
<intent-filter android:priority="102">
<action android:name="com.test.broadcast.use.helloworld"/>
</intent-filter>
</receiver>
<receiver android:name="com.test.broadcast.use.SecondBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.test.broadcast.use.helloworld"/>
</intent-filter>
</receiver>
...
</manifest>
private fun initDynamic(){
val intentFilter = IntentFilter()
intentFilter.addAction(MyAction)
intentFilter.priority = 101
registerReceiver(myDynamicBroadcastReceiver,intentFilter)
}
最后修改按钮发送有序广播,得到以下输出。可以看到优先输出了MyStaticBroadcastReceiver、最后输出SecondBroadcastReceiver。这就说明当设置的priority越大,则优先级越高,就会优先回调onReceive方法。

既然广播有优先级的接收消息,那么是否会有优先级高的拦截广播消息呢?答案是肯定有的,否则这个有序广播并没有什么卵用!接下来我们修改各个接收者中的onReceive方法。
首先,在MyStaticBroadcastReceiver类中。创建了一个Bundle对象,并且调用了setResultExtras方法,这个方法就是传递新的参数到下一个接收者。
companion object{
val OneKey = "one_key"
}
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("静态接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
val myBundle = Bundle()
myBundle.putString(OneKey,"哈哈,我是来自于MyStaticBroadcastReceiver的数据")
setResultExtras(myBundle)
}
}
其次,在MyDynamicBroadcastReceiver类中。需要调用getResultExtras方法来接收一个Bundle对象,并从中取值。这里传入true或者false只是会在优先级底的接收者获取Bundle对象是否为null,其他的并无特殊。然后继续构造了一个Bundle对象,传递了一个int类型的数据到下一个接收者。
companion object{
val TwoKey = "two_key"
}
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("动态接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
val getBundle = getResultExtras(true)
println("从MyStaticBroadcastReceiver获取的新增参数:${getBundle.get(MyStaticBroadcastReceiver.OneKey)}")
val myBundle = Bundle()
myBundle.putInt(TwoKey,111)
setResultExtras(myBundle)
}
}
最后,在SecondBroadcastReceiver类中,我们直接获取从上一个接收者传递过来的值。这里也需要注意的是,当优先级最高的广播接收者传递一个参数后,只能在优先级第二高的接收者接收到参数,其他的接收者无法继续收到。下面我们运行程序,可以得到以下输出。
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("SecondBroadcastReceiver接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
val getBundle = getResultExtras(true)
println("从MyDynamicBroadcastReceiver获取的新增参数:${getBundle.get(MyDynamicBroadcastReceiver.TwoKey)}")
}
}

再次修改MyDynamicBroadcastReceiver中的onReceive方法,在最后一行添加一个abortBroadcast(),这个方法就是拦截广播,不再发送,使得后面优先级较低的接收者无法收到广播消息。运行程序,得到以下输出。
override fun onReceive(context: Context?, intent: Intent?) {
if(MainActivity.MyAction == intent?.action){
println("动态接收者接收到广播,携带的参数:${intent.getStringExtra(MainActivity.Key)}")
val getBundle = getResultExtras(true)
println("从MyStaticBroadcastReceiver获取的新增参数:${getBundle.get(MyStaticBroadcastReceiver.OneKey)}")
val myBundle = Bundle()
myBundle.putInt(TwoKey,111)
setResultExtras(myBundle)
abortBroadcast()
}
}

onReceive方法中有继续传递参数,但是调用abortBroadcast()方法仍然可以阻止继续向下传递,这样在SecondBroadcastReceiver接收者不会有任何打印输出。
- 本地广播
以上广播都是全局广播,能够被系统其他应用接收到,自己的应用也能收到其他的广播,这就可能对我们自己的应用造成安全隐患。因此如果自己的应用无需和外界有相关联系,可以使用本地广播,应用中的广播接收者也只能收到自己应用中发出的广播。需要注意的是,本地广播无法使用静态注册。例子中使用到的MyLocalReceiver和动态注册的广播除了类名之外,其他完全一样,就不再单独贴出代码。
class MainActivity : AppCompatActivity() {
private var sendLocalBroadcastBtn: Button? = null
private lateinit var localBroadcastManager : LocalBroadcastManager
private val myLocalReceiver = MyLocalReceiver()
companion object{
val Key = "key"
val MyAction = "com.test.broadcast.use.helloworld"
val MyLocalAction = "com.test.broadcast.use.localaction"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendLocalBroadcastBtn = findViewById(R.id.send_local_broadcast_btn)
sendLocalBroadcastBtn?.setOnClickListener {
val myIntent = Intent(MyLocalAction)
myIntent.putExtra(Key,"i am local broadcast")
localBroadcastManager.sendBroadcast(myIntent)
}
initLocal()
}
/**
* 初始化本地广播
*/
private fun initLocal(){
localBroadcastManager = LocalBroadcastManager.getInstance(this)
val intentFilter = IntentFilter()
intentFilter.addAction(MyLocalAction)
intentFilter.priority = 99
localBroadcastManager.registerReceiver(myLocalReceiver,intentFilter)
}
override fun onStop() {
super.onStop()
localBroadcastManager.unregisterReceiver(myLocalReceiver)
}
}
从上面的MainActivity可以看出,不管是注册广播接收者,还是发送本地广播,都需要用到LocalBroadcastManager对象。注册本地广播和动态注册广播方式完全一样,需要创建IntentFilter对象进行过滤。发送广播需要调用LocalBroadcastManager对象的sendBroadcast方法,否则本地广播无法收到消息。

- 粘性广播
发送粘性广播调用sendStickyBroadcast方法,也可以发送粘性有序广播sendStickyOrderedBroadcast,由于发送粘性广播不提供安全性和保护性,所以Google已经不推荐使用任何粘性广播,本文也不再提供例子说明,感兴趣的可以参考其他的博客。
三、Android 8.0使用广播
Android 8.0 对应用在用户不与其直接交互时可以执行的操作施加了限制,应用在以下两方面受到了限制:
- 后台 Service 限制
- 广播限制
请注意:默认情况下,这些限制仅适用于适配 Android 8.0(API 级别 26)或更高版本的应用。 然而,即使应用适配的 API 级别低于 26,用户也可以从 Settings 为任意应用启用其中大多数限制。
这里主要说明广播限制。除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。说白了,就是说当应用的targetSdkVersion 26及以上,无法进行静态注册,只能动态注册广播接收者;如果把targetSdkVersion改为25及以下,但各大应用市场强制规定了targetSdkVersion版本要求,所以针对这些限制,我们能做出以下应对方式:1. 放弃静态注册;2.如果一定要静态注册, 发送的时候指定包名,即发送显式广播;3.发送广播的时候携带intent.addFlags(0x01000000),即能让广播突破隐式广播限制。
gitbub代码地址:github.com/leewell5717…