广播接收者
概念
BroadcastReceiver接收系统发出的广播,系统在运行过程中,会发生很多事件,系统为了让其他应用知道系统发生了这个事件,会发送一个对应该事件的广播,比如:电量改变,收到短信,拨打电话,屏幕解锁,系统开机。
原理
广播是通过intent发送的,intent中会携带一个action,当一条广播被发送出来时,系统是在所有清单文件中遍历,通过匹配意图过滤器找到能接收这条广播的广播接收者。
广播接收者所在进程即便没有启动,广播发送出来,系统也会启动这个进程,然后把广播交给广播接收者
定义方式
- 定义一个类继承BroadcastReceiver
- 在清单文件中进行配置,使用< intent-filter >标签下< action >标签指定接收对应广播的类型
广播接收者的特性
- 系统应用的广播接收者优先级不会超过1000
- 4.0之后,广播接收者所在的应用必须启动过一次,才能生效
- 4.0之后,如果广播接收者所在应用被用户手动关闭了,那么再也不会启动了,直到用户再次手动启动该应用。如果是系统关闭的,还是可以启动的
广播接收者例子
IP拨号器
原理
系统拨打号码时,会发出一个广播,广播中携带拨打的号码,接收拨打电话的广播,修改广播内携带的电话号码,再发送出去
实现
- 定义广播接收者接收打电话广播
- 在清单文件中定义该广播接收者接收的广播类型,注册
<receiver android:name=".CallReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
- 接收打电话广播需要权限
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
- 6.0以上的系统需要动态申请权限 Manifest.permission.PROCESS_OUTGOING_CALLS
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.PROCESS_OUTGOING_CALLS),100)
- 在定义的广播接收者中,获取拨打的号码,将号码进行修改,然后发送出去
class CallReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//获取拨打的电话
val data = resultData
println("拨打的电话是:${data}")
//发送修改后的电话
resultData = "888${data}"
}
}
- 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程
短信拦截器
原理
系统负责接收短信,短信应用并不会接收短信,它接收广播。系统收到短信时会产生一条广播,广播中包含了短信的号码和内容。但短信广播发出时,使用广播接收者接收该广播,获取此短信的号码,与黑名单中的号码进行比对,如果存在,则拦截广播,短信应用中就不会接收到此广播了。高版本无法实现拦截,即使优先级很高,系统短信应用仍然可以接收到短信
实现
- 定义接收短信的广播接收者
- 在清单文件中注册该广播,监听短信的action已被系统隐藏了,在上层应用中是无法显式调用的,需要使用 android.provider.Telephony.SMS_RECEIVED,注册此广播时,需要设置优先级,要保证优先级高于系统短信,才可以实现拦截
<receiver android:name=".SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
- 需要申请接收短信的权限
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
- 6.0以上的系统需要动态申请接收短信的权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECEIVE_SMS),100)
- 获取接收到的短信,系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
class SmsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent ?: return
println("收到短信")
val bundle = intent.extras
bundle?.let {
//如果短信太长,则会分成多条消息发送过来,所以是数组
val objs = it.get("pdus") as Array<Any>
objs.forEach {obj->
val smsMessage= SmsMessage.createFromPdu(obj as ByteArray)
val address = smsMessage.originatingAddress
val messageBody = smsMessage.messageBody
println("address:${address},messageBody:${messageBody}")
}
}
}
}
SD卡监听
原理
监听sd卡的状态,对于下载文件到sd卡是非常有必要的,根据不同的状态,对于实际的应用场景,做出对应的逻辑处理
实现
- 定义一个监听sd卡状态的广播接收者
- 在清单文件中注册该广播接收者,监听的action是
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<!-- 还需要匹配一个scheme -->
<data android:scheme="file"/>
- 不需要权限
- 在广播接收者中分别处理不同的状态
class SdReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when( intent?.action){
Intent.ACTION_MEDIA_MOUNTED->{
Toast.makeText(context,"sd卡就绪",Toast.LENGTH_SHORT).show()
}
Intent.ACTION_MEDIA_UNMOUNTED->{
Toast.makeText(context,"sd卡被卸载了",Toast.LENGTH_SHORT).show()
}
Intent.ACTION_MEDIA_REMOVED->{
Toast.makeText(context,"sd卡被拨出了",Toast.LENGTH_SHORT).show()
}
}
}
}
开机启动
原理
监听开机启动事件可以做一些事情,可以启动指定的应用。系统开机时,系统会发出开机广播
实现
- 定义一个广播接收者,接收开机启动
- 在清单文件中注册该广播,过滤action
<action android:name="android.intent.action.BOOT_COMPLETED"/>
- 需要在清单文件中添加开机权限,无需动态申请权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
println("已开机")
}
}
应用安装卸载
原理
应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名
实现
- 定义一个广播接收者,接收应用的安装卸载和更新
- (8.0以下)在清单文件中注册该广播,对应的action
<receiver android:name=".AppReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<!--这里需要scheme为package才可以启动-->
<data android:scheme="package" />
</intent-filter>
</receiver>
- Android 8.0去掉了部分隐式广播,例如网络的变化、app的安装和卸载等,所以不能使用静态注册了,必须使用动态注册
val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED)
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
intentFilter.addDataScheme("package")
appReceiver = AppReceiver()
registerReceiver(appReceiver, intentFilter)
- 在广播接收者中处理对应的action
class AppReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val packName = intent?.data?.schemeSpecificPart
when (intent?.action) {
Intent.ACTION_PACKAGE_ADDED -> {
Toast.makeText(context, "${packName}应用已安装,哈哈", Toast.LENGTH_SHORT).show()
}
Intent.ACTION_PACKAGE_REPLACED -> {
Toast.makeText(context, "${packName}应用已更新,哈哈", Toast.LENGTH_SHORT).show()
}
Intent.ACTION_PACKAGE_REMOVED -> {
Toast.makeText(context, "${packName}应用已卸载,哈哈", Toast.LENGTH_SHORT).show()
}
}
}
}
发送自定义广播
概念
发送广播是为了通知系统中的应用发生了什么事,一般自己写的软件很少使用自定义广播的,一般都是系统通知其他应用的
- 自定义广播
private fun sendBroadcastReceiver() {
val intent = Intent()
intent.action = "a.b.c"
sendBroadcast(intent)
}
- 在清单文件中,注册广播接收者
广播接收者的注册
清单文件注册
广播接收者永远生效,除非卸载应用,或者手动停止进程
代码注册
需要广播接收者时,注册,不需要时反注册,广播就失效了
特殊广播接收者,必须使用代码注册:屏幕点亮和关闭,电量的改变