[TOC]
前言
广播有两种注册方式:一种是在清单注册,这种我们也称为静态注册,一种是在运行时调用 registerReceiver() 方法注册,这种我们也称为动态注册。
广播的发送根据到达顺序有两种不同的类型:一种是普通广播,一种是有序广播。
广播的发送根据是否指定接收者也可分为两种不同的类型:一种是显示广播,一种是隐式广播。
注意: 如果我们的广播只是在应用内部使用,那我们可以使用本地广播。这个实现更加高效(不需要进程间通信),而且也不需要担心任何与其他应用程序能够接受和发送我们广播有关的安全问题。
BroadcastReceiver 生命周期
生命周期:我们通过组件发送一个广播,系统会自动匹配对应的已经注册的广播接收者,调用广播接收者的onReceiver() 方法,广播接收者 onReceiver() 方法运行完毕后,系统将会认定此广播接收者对象不在是一个活动的对象,也就会 finished 掉它。
注意: 由于BroadcastReceiver 是运行在ui线程的所以不能再 onReceiver() 方法中执行耗时操作,否则会弹出 ANR(application No Response 应用无响应)的对话框。也不要在onReceiver()方法中启动线程,然后从函数返回,因为一旦它返回,系统就会认为BroadcastReceiver不在活动,因此不再需要它的宿主进程(除非其中的其他应用程序组件是活动的)。因此,系统可能会在任何时候终止进程已回收内存,这样做会终止进程中运行的派生线程。
那我们需要在BroadcastReceiver中完成一项耗时操作有没有什么办法呢,这个是有的官方提供了两种方法:
1. 调用 goAsync() 方法,
调用 goAsync() 接收者的 onReceiver() 方法并将 BroadcastReceiver.PendingResult 给后台线程。这使得广播在返回后保持活动状态 onReceive()。但是,即使使用这种方法,系统也希望您能够非常快速地完成广播(10秒以内)。它运行您将工作移动到另一个线程,以避免阻塞主线程。 示例如下:
class IBroadcastReceiverTask : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
KLog.i("IBroadcastReceiverTask")
//避免卡死的发生
val pendingResult = goAsync()
val task = Task(pendingResult, intent)
//执行异步任务
task.execute()
}
private class Task(private val pendingResult:PendingResult,
private val intent: Intent?
):AsyncTask<String,String,String>(){
override fun doInBackground(vararg params: String?): String {
KLog.i("doInBackground")
ssss()
return toString()
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
//必须调用 finish() 这样 BroadcastReceiver 才能被回收
pendingResult.finish()
KLog.i("完成耗时操作")
}
}
}
fun ssss(){
var time = 30
do {
Thread.sleep(1000)
KLog.i(time)
time--
}while (time!=0)
}
复制代码
上面是第一种方法的示例
2. 使用JobService
用JobScheduler JobService,这样系统就知道在这个过程中还有活动的工作要做。 示例如下:
class IBroadcastReceiverJobService: BroadcastReceiver() {
var JOB_TEST =10001
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onReceive(context: Context?, intent: Intent?) {
KLog.i("IBroadcastReceiverJobService")
val jobScheduler:JobScheduler = context?.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(JOB_TEST, ComponentName(context.packageName, MyJobService::class.java.getName()))
.setPeriodic(AlarmManager.INTERVAL_FIFTEEN_MINUTES)
.setPersisted(false)
.build()
jobScheduler.schedule(jobInfo)
KLog.i("IBroadcastReceiverJobService 完成")
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class MyJobService : JobService(){
override fun onStopJob(params: JobParameters?): Boolean {
KLog.i("onStopJob ===> 结束")
return false
}
override fun onStartJob(params: JobParameters?): Boolean {
KLog.i("onStartJob ===> 开始")
object : AsyncTask<String, String, String>() {
override fun doInBackground(vararg params: String?): String {
ssss()
return toString()
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
KLog.i("onPostExecute ===> 开始")
this@MyJobService.jobFinished(params,false)
}
}.execute()
KLog.i("onStartJob ===> 结束")
return false
}
}
复制代码
这里需要注意一下在 AndroidManifest.xml 中声明JobService 的时候一定要加上 "android.permission.BIND_JOB_SERVICE" 这个权限,不然运行时会报错。 声明如下:
<service
android:permission="android.permission.BIND_JOB_SERVICE"
android:name=".MyJobService"/>
复制代码
还有就是JobService 是在Android 5.0(API级别21)才加入的 注意适配低版本。
想要知道更详细的JobScheduler 用法 请查看panzeyong.com/2017/05/21/…
在清单注册(静态注册)
清单注册:也可称为静态注册,就是在 AndroidManifest.xml 中声明广播接收器。此类广播在应用尚未启动的时候就可以接受到相应的广播。
那如何注册呢,如下:
//创建一个类 继承BroadcastReceiver
class IBroadcastReceiver1: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
KLog.i("onReceive ===> ${javaClass.simpleName}")
StringBuilder().apply {
append("Action: ${intent?.action}\n")
append("URI: ${intent?.toUri(Intent.URI_INTENT_SCHEME)}\n")
toString().also { log ->
KLog.d(TAG, log)
Toast.makeText(context, log, Toast.LENGTH_LONG).show()
}
}
}
companion object {
private const val TAG = "IBroadcastReceiver1"
}
}
复制代码
在AndroidManifest.xml中用声明创建的类
<receiver android:name=".IBroadcastReceiver1" >
<intent-filter android:priority="666">
<action android:name="com.hugo.IBroadcastReceiver1"/>
</intent-filter>
</receiver>
复制代码
这样我们就通过清单注册了一个叫 IBroadcastReceiver1 的广播。
动态注册
动态注册:也就是在Service 或者Activity等组件中,通过Context.registerReceiver()注册广播接收器。此类广播接收器是在应用已经启动后,通过代码进行注册的。
示例如下:
//创建一个 动作 过滤器
val filter = IntentFilter()
//设置监听 动作 可以设置多个
filter.addAction(Constants.FILTER_NAME2)
// 设置优先级 取值范围 -1000 到 1000 数值越大 优先级越高
filter.priority = 1000
//注册广播监听器
registerReceiver(IBroadcastReceiver2(),filter)
复制代码
这样我们就动态的注册了一个广播接收者。
注意: 在退出或关闭广播注册的组件是,务必调用 unregisterReceiver() 方法解除注册,不是容易造成广播接收器的泄漏。
普通广播
普通广播是完全异步的,可以在同一时刻(逻辑上)被所有的接收者接受到,消息传递的效率比较高,但是缺点是:接收者不能处理结果传递给下一个接收者,并且无法终止广播Intent的传递。
那我们怎么发送广播呢 如下例:
val intent = Intent()
//设置 广播动作
intent.action = Constants.FILTER_NAME2
//发送广播
sendBroadcast(intent)
日志
IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:16)#OnReceive ] onReceive ===> IBroadcastReceiver2
IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:21)#OnReceive ] Action: IBroadcastReceiver2
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:16)#OnReceive ] onReceive ===> IBroadcastReceiver3
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:24)#OnReceive ] Action: IBroadcastReceiver2
复制代码
如上例:调用Context.sendBroadcast() 方法,发送普通广播,只要是符合Intent中设置的 action 的广播接收者都可以进行对应的处理。
总结:
- 无视优先级,动态广播接收器优先于静态广播接收器;
- 清单注册:先扫描的优先于后扫描的;
- 动态注册:先注册优先于后注册的。
有序广播
有序广播:是按照广播接收者在注册时设置的优先级别,依次接收广播。优先级高的接收者可以根据不同的需求修改数据或者中断广播。
那么怎么设置优先级呢,就在在注册广播接收者是 设置 priority的值取值范围是 -1000到1000,默认值为0。
发送广播如下例子:
val intent = Intent()
intent.action = Constants.FILTER_NAME2
sendOrderedBroadcast(intent,null)
日志如下:
IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:17)#OnReceive ] onReceive ===> IBroadcastReceiver2
IBroadcastReceiver2.kt: [ (IBroadcastReceiver2.kt:25)#OnReceive ] Action: IBroadcastReceiver2
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:16)#OnReceive ] onReceive ===> IBroadcastReceiver3
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:18)#OnReceive ] msg ===> IBroadcastReceiver2 这是上一个接收器传过来的信息
IBroadcastReceiver3.kt: [ (IBroadcastReceiver3.kt:25)#OnReceive ] Action: IBroadcastReceiver2
复制代码
如示例所示 调用Context.sendOrderedBroadcast() 方法发送广播,系统就会根据广播接收者的优先级别依次执行,前面的广播有权终止广播,这样后面的接收者就收不到该广播了,前面的广播也可以向后面的广播接收者发送数据。
广播接收者调用abortBroadcast() 该方法就可以终止该广播。
广播接收者向后发送数据的方法有 setResultExtras() 和 setResult() 两个方法。
广播接收者获取前面发送的数据是使用 getResultExtras(true) 该方法。
注意: 以上方法都是在onReceive() 该方法里调用的。
总结:
- 优先级高的先接收;
- 同优先级动态注册优先于静态注册;
- 同优先级同类广播接收者,静态注册:先扫描优先于后扫描的,动态注册 :先注册优先于后注册的。
本地广播
本地广播:顾名思义就是该广播只在应用内部有用。
本地广播主要使用LocalBroadcastManager 类中的方法:
- registerReceiver():这个是用与注册广播接收器的。
- sendBroadcast():这个是用于发送广播的。
- sendBroadcastSync():这个和 sendBroadcast()方法类似,但是如果有用于Intent的接收器,该函数将阻塞并在返回之前立即分派它们。
- unregisterReceiver():这个方法是用于解除注册用的。
以上就是本地广播所有方法了。使用方法和前面介绍的基本一样。
显示广播
显示广播:发送的Intent是显示Intent的广播,同过指定Intent组件名称来实现,它一般用在知道目标组件名称的前提下,意图明确,指定了激活的广播接收者,所以一般是在应用内部使用。
示例如下:
val intent = Intent()
intent.setClass(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)
val intent = Intent(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)
val intent = Intent()
intent.setClassName(this,IBroadcastReceiver1::class.java.name)
sendBroadcast(intent)
//上面三种方法的内部实现都是 创建了ComponentName对象 并赋值给intent.component
val intent = Intent()
intent.component = ComponentName(this,IBroadcastReceiver1::class.java)
sendBroadcast(intent)
复制代码
以上就是显示广播的使用方法。
隐式广播
隐式广播 :就是同过过滤器(Intent Filter)来实现,它一般是没有指出广播接收者的名称,Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URL和数据类型)进行匹配找到最适合的接收者来处理这个意图。这个一般用于不同应用之间。
示例如下:
//注册接收者
val filter = IntentFilter()
//设置优先级
filter.priority = 666
//设置数据类型
filter.addDataType("text/plain")
//设置URL
filter.addDataScheme("https")
// 设置类别
filter.addCategory("android.intent.category.DEFAULT")
registerReceiver(IBroadcastReceiver4(),filter)
//发出隐式广播
val intent = Intent()
intent.action = Constants.FILTER_NAME2
//设置类别
intent.addCategory("android.intent.category.DEFAULT")
//设置数据类型
//intent.type = "text/plain"
//设置URL
//intent.data = Uri.parse("https://www.baidu.com")
// 设置 数据类型和URL
intent.setDataAndType(Uri.parse("https://www.baidu.com"),"text/plain")
sendBroadcast(intent)
复制代码
上面就是隐式广播了。
注意:
- 在Android 7.0(API级别24)开始系统对广播做了一些限制:
- 不能发送 ACTION_NEW_PICTURE和ACTION_NEW_VIDEO广播。
- 必须使用registerReceiver(BroadcastReceiver,IntentFilter)注册CONNECTIVITY_ACTION广播。使用静态声明接收者无效。
- 在Android 8.0(API级别26)开始系统对清单声明的接收器的限制进一步的增强了,除了少部分隐式广播不限制外,其他的所有隐式广播均无法通过 AndroidManifest.xml中声明,不过可以通过显示调用。官方推荐使用动态注册对广播进行注册。
- 在Android 9.0(API级别28)开始, NETWORK_STATE_CHANGED_ACTION 广播不会收到有关用户位置或个人身份数据的信息。此外应用安装在运行Android9.0或更高版本的设备上,来着WI-Fi的系统广播不再包含 SSID、BSSID、连接信息或扫描结果。需要获取这些信息,可以调用 getConnectionInfo()获取。
隐式广播例外情况
关于清单注册隐式广播的例外情况: 官网
// 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
*/
"保留原因:如果应用需要了解这些 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短信应用需要接收这些广播"
复制代码
参考链接
developer.android.com/guide/compo…
本文demo 链接 github.com/tao11122233…