其实,从BroadcastReceiver广播接收者这个名字来看,我们可以猜测其功能是接受和监听广播的发送,然后进行业务处理,那么我们要想理解BroadcastRecevier就必须首先认识广播机制,认识什么是广播,广播是怎么发送的。
为了便于进行系统级别的消息通知,Android引入了广播机制。Android应用程序可以从Android系统和其他Android应用程序发送或接收广播消息,类似于 发布-订阅 设计模式。我们可以在代码中注册相关广播的监听,然后在我们关注的事件发生时,发送对应的广播,就会触发代码中的监听事件,进而进行相应的逻辑处理。
一般来说,广播可用作跨应用程序和普通用户流之外的消息传递系统。但是,在使用过程必须小心,不要滥用机会在后台响应广播和运行作业,这可能会导致系统性能下降。
在学习如何发送和接受广播之前,首先我们需要了解下广播的类型,广播主要分为标准广播和有序广播两类。
标准广播就是普通广播,一般情况下我们均使用标准广播。标准广播是一种完全异步的广播,在广播发出后,所有广播的接收者几乎都会在同一时刻接收到广播消息,因此它们之间没有任何先后顺序可言,我们也无法确定其接收广播的先后顺序。这种广播的方式效率会比较高,但是由于无法确定接收广播的先后顺序,也意味着它是无法被截断的。如下图所示:
与标准广播不同,有序广播是一种同步广播,即广播的接受是串行的,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接受器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。但是,这种广播方式由于需要依次接收广播,因此效率会比较低。如下图所示:
了解过Android的广播机制后,接下来我们就可以正式来学习BroadcastReceiver了。使用BroadcastReceiver来实现通信主要也分为以下几个步骤:
接下来,我们以对网络状态变化的系统广播的监听为例,来实现BroadcastReceiver。
要使用广播接收器,首先需要继承BroadcastReceiver类并实现onReceiver方法,在onReceiver方法中完成接收到广播后的业务逻辑处理,但是,需要注意onReceiver是在主线程中执行的,因此在执行长耗时任务时需要使用子线程进行异步处理,如果onReceiver中的执行时间超过10s就会出现ANR错误。具体实现代码如下:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//监听到广播消息
Log.e("test_bug", "network state changed")
}
}
此处,我们重写了onReceive方法,当系统网络状态发生变化时,只要我们注册了该MyReceiver监听系统广播,那么onReceive就会被调用。
要使用BroadcastReceiver首先要对其进行注册,从而监听到广播消息。在Android开发中,存在动态注册和静态注册两种BroadcastReceiver的注册方式。
其实动态注册就是在代码中调用registerReceiver方法对BroadcastReceiver进行注册,因此动态注册的BroadcastReceiver其生命周期通常是依赖于注册的Activity或者是Service组件的,当Activity被销毁时,BroadcastReceiver往往也会被解注册尽早释放内存,以防内存泄漏。
和Activity以及Service的启动相同,广播的注册、监听和发送都是依赖于Intent实现的,因此在进行动态注册BroadcastReceiver时,我们只需要指定广播接收者以及IntentFilter,即使用Intent中的Action、Category和Data来过滤广播,接收所需广播即可。代码如下:
val intentFilter = IntentFilter()//过滤
val myReceiver = MyReceiver()//接收器
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE")//指定过滤规则,只接收网络状态变化广播
registerReceiver(myReceiver, intentFilter)//注册
如上述代码所示,只要指定intent过滤规则,我们即可借此来监听对应的广播,intent的过滤规则与启动Activity相同,也就是说,其实一个BroadcastReciver其实是可能接收多个不同的广播的。注册完成后,只要系统网络状态发生变化,发送了“android.net.conn.CONNECTIVITY_CHANGE”广播,那么注册的MyReceiver就能够监听到并调用onReceive方法。如下图,切换手机的网络连接,onReceiver方法被调用:
由于动态注册是依赖于Activity的声明周期的,因此退出应用销毁Activity后,或者是主动调用unregisterReceiver方法后,广播无法再监听到。注意,只要使用了动态注册方法,那么我们就应该在不需要使用广播的时候将其进行解注册操作。
unregisterReceiver(myReceiver)
BroadcastReceiver也可以采用静态注册的方式,如Activity一般在AndroidManifest使用receiver标签进行声明注册,并指定BroadcastReceiver的过滤规则。代码如下:
<receiver
android:name=".broadcastreceiver.MyReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
intent-filter>
receiver>
与Activity和Service在进行声明是基本相同,只要在intent-filter中指定过滤规则,即可监听到对应的广播。其中enabled属性表示是否启动该广播接收器,exported表示是否接收应用外的广播。
注意:在Android8.0及以上版本,静态注册广播被严格限制,除了部分几种系统广播以外,其余系统广播和自定义的隐式广播均无法再使用静态注册监听的方式进行接收。具体可参考Android官方文档,如下:
// Android 8.0 上不限制的隐式广播
/**
开机广播
Intent.ACTION_LOCKED_BOOT_COMPLETED
Intent.ACTION_BOOT_COMPLETED
*/
"保留原因:这些广播只在首次启动时发送一次,并且许多应用都需要接收此广播以便进行作业、闹铃等事项的安排。"
/**
增删用户
Intent.ACTION_USER_INITIALIZE
"android.intent.action.USER_ADDED"
"android.intent.action.USER_REMOVED"
*/
"保留原因:这些广播只有拥有特定系统权限的app才能监听,因此大多数正常应用都无法接收它们。"
/**
时区、ALARM变化
"android.intent.action.TIME_SET"
Intent.ACTION_TIMEZONE_CHANGED
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
"保留原因:时钟应用可能需要接收这些广播,以便在时间或时区变化时更新闹铃"
/**
语言区域变化
Intent.ACTION_LOCALE_CHANGED
*/
"保留原因:只在语言区域发生变化时发送,并不频繁。 应用可能需要在语言区域发生变化时更新其数据。"
/**
Usb相关
UsbManager.ACTION_USB_ACCESSORY_ATTACHED
UsbManager.ACTION_USB_ACCESSORY_DETACHED
UsbManager.ACTION_USB_DEVICE_ATTACHED
UsbManager.ACTION_USB_DEVICE_DETACHED
android.hardware.usb.action.USB_STATE
*/
"保留原因:如果应用需要了解这些 USB 相关事件的信息,目前尚未找到能够替代注册广播的可行方案"
/**
蓝牙状态相关
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED
BluetoothDevice.ACTION_ACL_CONNECTED
BluetoothDevice.ACTION_ACL_DISCONNECTED
*/
"保留原因:应用接收这些蓝牙事件的广播时不太可能会影响用户体验"
/**
Telephony相关
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED
TelephonyIntents.SECRET_CODE_ACTION
TelephonyManager.ACTION_PHONE_STATE_CHANGED
TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED
TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED
*/
"保留原因:设备制造商 (OEM) 电话应用可能需要接收这些广播"
/**
账号相关
AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION
*/
"保留原因:一些应用需要了解登录帐号的变化,以便为新帐号和变化的帐号设置计划操作"
/**
应用数据清除
Intent.ACTION_PACKAGE_DATA_CLEARED
*/
"保留原因:只在用户显式地从 Settings 清除其数据时发送,因此广播接收器不太可能严重影响用户体验"
/**
软件包被移除
Intent.ACTION_PACKAGE_FULLY_REMOVED
*/
"保留原因:一些应用可能需要在另一软件包被移除时更新其存储的数据;对于这些应用,尚未找到能够替代注册此广播的可行方案"
/**
外拨电话
Intent.ACTION_NEW_OUTGOING_CALL
*/
"保留原因:执行操作来响应用户打电话行为的应用需要接收此广播"
/**
当设备所有者被设置、改变或清除时发出
DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED
*/
"保留原因:此广播发送得不是很频繁;一些应用需要接收它,以便知晓设备的安全状态发生了变化"
/**
日历相关
CalendarContract.ACTION_EVENT_REMINDER
*/
"保留原因:由日历provider发送,用于向日历应用发布事件提醒。因为日历provider不清楚日历应用是什么,所以此广播必须是隐式广播。"
/**
安装或移除存储相关广播
Intent.ACTION_MEDIA_MOUNTED
Intent.ACTION_MEDIA_CHECKING
Intent.ACTION_MEDIA_EJECT
Intent.ACTION_MEDIA_UNMOUNTED
Intent.ACTION_MEDIA_UNMOUNTABLE
Intent.ACTION_MEDIA_REMOVED
Intent.ACTION_MEDIA_BAD_REMOVAL
*/
"保留原因:这些广播是作为用户与设备进行物理交互的结果:安装或移除存储卷或当启动初始化时(当可用卷被装载)的一部分发送的,因此它们不是很常见,并且通常是在用户的掌控下"
/**
短信、WAP PUSH相关
Telephony.Sms.Intents.SMS_RECEIVED_ACTION
Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION
注意:需要申请以下权限才可以接收
"android.permission.RECEIVE_SMS"
"android.permission.RECEIVE_WAP_PUSH"
*/
"保留原因:SMS短信应用需要接收这些广播"
注意:自Android3.1开始,系统本身则增加了对所有app当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的app,同样无法接收到广播。
在Android系统中,为了让应用能够更好的工作,系统为各种系统事件都提供了广播,每当事件发生时系统就会发送对应的广播。在上述小节里,我们其实就时针对系统网络连接状态变化的广播进行了监听。一些常见的系统广播如下:
以上是一些常用的系统广播,但实际上,系统广播远远不止这些,需要时可以查看相关文档。
在上述的例子中,我们学习了使用BroadcastReceiver接受系统广播的方法,但是在实际开发中,我们对于BroadcastReceiver的用法更多是用于实现不同组件之间的通信,所以我们还需要学习以下如何在应用程序中发送自定义的广播。前面已经介绍过了,广播注意分为两种类型:标准广播和有序广播,其就是通过不同的发送方式来区分的,接下来我们将分别学习发送两种广播的方式。
接收自定义广播和接收系统广播的方式完全相同,我们所需要做的就是自定义并注册BroadcastReceiver即可。不同的就是,系统广播不需要我们自己发送,而自定义广播需要我们自己发送。
事实上,发送广播的方式和隐式启动Activity以及Service的方式相同,均是通过Intent来实现的,我们只需要指定发送广播的action等相关规则(一般对于自定义广播我们只需要指定action即可),然后调用sendBroadcast即可发送标准广播。另外,由于广播是使用Intent传递的,所以我们也可以通过intent传递一些数据给广播接收器,在onReceive方法中接收到。代码如下:
val intent = Intent("mine_action")
intent.putExtra("str", "test message")
intent.putExtra("int", 101)
sendBroadcast(intent)
注册广播接收者时,我们只需要指定对应的action即可,动态注册代码如下:
val intentFilter = IntentFilter()//过滤
intentFilter.addAction("mine_action")//指定过滤规则,只接收网络状态变化广播
registerReceiver(myReceiver, intentFilter)//注册
onReceive方法实现如下:
override fun onReceive(context: Context?, intent: Intent?) {
Log.e("test_bug", "onReceive")
if (intent != null){
val n = intent.getIntExtra("int", -1)
val s = intent.getStringExtra("str")
Log.e("test_bug", "receive message:---int:$n---str:$s")
}
}
点击发送广播,结果如下图:
需要注意的是,对应Android8.0及以上版本,我们无法再使用静态注册BroadcastReceiver的方式来接收自定义隐式广播了。但是,这并不意味着我们无法再使用静态注册方法,这只是针对隐式广播的,也就是说我们可以发送显示广播,如显示启动Activity一样,我们需要显示说明BroadcastReceiver的包名和类名。具体实现如下:
val intent = Intent("mine_action")
intent.putExtra("str", "test message")
intent.putExtra("int", 101)
//兼容Android8.0及以上版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//硬编码绕过对隐式广播的限制,不推荐
// intent.addFlags(0x01000000)
//指定组件,也适用于给其他应用的广播接收者发送消息(指定应用的包名、指定类的全类名)
// intent.setComponent(ComponentName(this@BroadcastReceiverActivity, MyReceiver::class.java))
//指定类名
intent.setClassName(this, MyReceiver::class.java.name)
}
sendBroadcast(intent)
结果如下图所示:
此时,若我们也进行了动态注册,那么动态注册的广播接收者优先接收到消息,如下图:
多个广播接收者的标准广播接收顺序规则:
发送有序广播的实现方式和标准广播基本相同,其区别就是不是调用sendBroadcast而是sendOrderedBroadcast。具体使用代码如下:
val intent = Intent("mine_action")
intent.putExtra("str", "test OrderedBroadcast")
intent.putExtra("int", 102)
sendOrderedBroadcast(intent, null)
结果如下:
我们可以看到在只有一个BroadcastReceiver时,有序广播和标准广播是完全一样的,其区别主要存在于当一个广播被多个BroadcastReceiver监听时,有序广播会根据根据优先级进行传播而且能够进行拦截操作。代码如下:
自定义三个BroadcastReceiver类,如下:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//监听到广播消息
Log.e("test_bug", "onReceive1----priority:10")
if (intent != null){
val n = intent.getIntExtra("int", -1)
val s = intent.getStringExtra("str")
Log.e("test_bug", "receive message:---int:$n---str:$s")
}
}
}
class MyReceiver2 : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//监听到广播消息
Log.e("test_bug", "onReceive2----priority:40")
if (intent != null){
val n = intent.getIntExtra("int", -1)
val s = intent.getStringExtra("str")
Log.e("test_bug", "receive message:---int:$n---str:$s")
}
}
}
class MyReceiver3 : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//监听到广播消息
Log.e("test_bug", "onReceive3----priority:30")
if (intent != null){
val n = intent.getIntExtra("int", -1)
val s = intent.getStringExtra("str")
Log.e("test_bug", "receive message:---int:$n---str:$s")
}
}
}
在AndroidManifest中进行静态注册,并为每个广播接收者声明priority属性,具体如下:
<receiver
android:name=".broadcastreceiver.MyReceiver3"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="30">
<action android:name="mine_action"/>
intent-filter>
receiver>
<receiver
android:name=".broadcastreceiver.MyReceiver2"
android:enabled="true"
android:exported="true" >
<intent-filter android:priority="40">
<action android:name="mine_action"/>
intent-filter>
receiver>
<receiver
android:name=".broadcastreceiver.MyReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter android:priority="10">
<action android:name="mine_action"/>
intent-filter>
receiver>
运行结果如下:
我们可以看到,priority属性值越大的BroadcastReceiver越早接收到广播消息。那么如果存在BroadcastReceiver的priority相同呢?我们将MyReceiver3和MyReceiver2同样设置为10,结果如下:
我们可以看到,此时其接收到广播消息的顺序时根据其在AndroidManifest中的注册顺序来的。而如果存在BroadcastReceiver没有设置priority呢?我们将MyReceiver3和MyReceiver2的priority值去掉,结果如下图所示:
此时,没有设置priority的优先级低,都没有设置priority时按照注册顺序确定优先级。
在发送有序广播时,我们可以随时在onReceiver中使用abortBroadcast对广播进行拦截,拦截后,优先级低的还没有接收的广播的BroadcastReceiver将无法接收到广播。我们以上述代码为例,在MyReceiver3中进行拦截,具体如下:
class MyReceiver3 : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//监听到广播消息
Log.e("test_bug", "onReceive3----priority:30")
if (intent != null){
val n = intent.getIntExtra("int", -1)
val s = intent.getStringExtra("str")
Log.e("test_bug", "receive message:---int:$n---str:$s")
}
//拦截广播
abortBroadcast()
}
}
结果如下:
可以看到,优先级更低的MyReceiver在广播被拦截后无法再接收到消息。
通过上述测试,我们可以总结出有序广播的传递顺序如下:
广播本身是一种跨进程的通信方式,可以实现不同应用之间的通信,我们前面所发送的广播都是系统全局广播,能够被所有应用接收到。但是在我们的开发中除了对系统广播的监听,我们往往只需要实现应用间不同组件之间的通信即可,如果我们发送全局广播的话就可能会导致一些安全性问题,为了解决这个问题,Android引入了本地广播机制,即应用内的广播,本地广播只能在应用内传播,而广播接收者也只能接收来自本应用发出的广播。
本地广播的用法并不复杂,主要就是使用了一个LocalBroadcastManager来对广播进行管理,并提供了相应的发送广播和注册广播接收器的方法。注册广播的方式具体如下:
val intentFilter = IntentFilter()//过滤
intentFilter.addAction("mine_action")//指定过滤规则,只接收网络状态变化广播
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, intentFilter)//本地广播注册
发送本地广播代码如下:
val intent = Intent("mine_action")
intent.putExtra("str", "test message")
intent.putExtra("int", 101)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
解注册的方法如下:
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
结果如下图所示:
我们可以发现,实际上本地广播的使用方法与我们之前所描述的一般的广播没有本质区别,只是使用的方法都是LocalBroadcastManager中的。
与一般的广播相比,本地广播主要存在以下几个特点:
在学习发送有序广播时,我们发现sendOrderedBroadcast存在两个参数,其另外一个参数为一个String类型的值,事实上sendBroadcast也可以传递该参数,这个参数为对接收广播信息的应用的权限限制,即只有拥有该权限的应用才能接收到该广播。如下:
sendOrderedBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
此时,若我们需要接收到该广播,我们必须申请SEND_SMS权限,如下:
<uses-permission android:name="android.permission.SEND_SMS"/>
其实,我们也可以对接收者进行权限限制,在注册的时候声明所需权限,那么就只有在发送的广播中存在该权限才会接收。如下:
<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
intent-filter>
receiver>
//动态注册
var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
此时,只有发送广播的应用中存在SEND_SMS权限,BroadcastReceiver才会接收。
在Android开发过程中,我们往往将BroadcastReceiver用于不同组件之间的异步通信,比如在使用startService方法启动Service时,我们可以通过BroadcastReceiver来实现Service和Activity的通信。相比Activity和Service,BroadcastReceiver的内容相对简单,但是在使用的时候我们应该注意在各个版本中针对广播的一些变化,否则可能会导致一些问题。