Android BroadcastReceiver(广播)

[TOC]html

前言

广播有两种注册方式:一种是在清单注册,这种咱们也称为静态注册,一种是在运行时调用 registerReceiver() 方法注册,这种咱们也称为动态注册。java

广播的发送根据到达顺序有两种不一样的类型:一种是普通广播,一种是有序广播android

广播的发送根据是否指定接收者也可分为两种不一样的类型:一种是显示广播,一种是隐式广播git

注意: 若是咱们的广播只是在应用内部使用,那咱们可使用本地广播。这个实现更加高效(不须要进程间通讯),并且也不须要担忧任何与其余应用程序可以接受和发送咱们广播有关的安全问题。github

BroadcastReceiver 生命周期

生命周期:咱们经过组件发送一个广播,系统会自动匹配对应的已经注册的广播接收者,调用广播接收者的onReceiver() 方法,广播接收者 onReceiver() 方法运行完毕后,系统将会认定此广播接收者对象不在是一个活动的对象,也就会 finished 掉它。 安全

image

注意: 因为BroadcastReceiver 是运行在ui线程的因此不能再 onReceiver() 方法中执行耗时操做,不然会弹出 ANR(application No Response 应用无响应)的对话框。也不要在onReceiver()方法中启动线程,而后从函数返回,由于一旦它返回,系统就会认为BroadcastReceiver不在活动,所以再也不须要它的宿主进程(除非其中的其余应用程序组件是活动的)。所以,系统可能会在任什么时候候终止进程已回收内存,这样作会终止进程中运行的派生线程。app

那咱们须要在BroadcastReceiver中完成一项耗时操做有没有什么办法呢,这个是有的官方提供了两种方法:异步

1. 调用 goAsync() 方法,

调用 goAsync() 接收者的 onReceiver() 方法并将 BroadcastReceiver.PendingResult 给后台线程。这使得广播在返回后保持活动状态 onReceive()。可是,即便使用这种方法,系统也但愿您可以很是快速地完成广播(10秒之内)。它运行您将工做移动到另外一个线程,以免阻塞主线程。 示例以下:ide

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 的广播接收者均可以进行对应的处理。

总结:

  1. 无视优先级,动态广播接收器优先于静态广播接收器;
  2. 清单注册:先扫描的优先于后扫描的;
  3. 动态注册:先注册优先于后注册的。

有序广播

有序广播:是按照广播接收者在注册时设置的优先级别,依次接收广播。优先级高的接收者能够根据不一样的需求修改数据或者中断广播。

那么怎么设置优先级呢,就在在注册广播接收者是 设置 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() 该方法里调用的。

总结:

  1. 优先级高的先接收;
  2. 同优先级动态注册优先于静态注册;
  3. 同优先级同类广播接收者,静态注册:先扫描优先于后扫描的,动态注册 :先注册优先于后注册的。

本地广播

本地广播:顾名思义就是该广播只在应用内部有用。

本地广播主要使用LocalBroadcastManager 类中的方法:

  1. registerReceiver():这个是用与注册广播接收器的。
  2. sendBroadcast():这个是用于发送广播的。
  3. sendBroadcastSync():这个和 sendBroadcast()方法相似,可是若是有用于Intent的接收器,该函数将阻塞并在返回以前当即分派它们。
  4. 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)
复制代码

上面就是隐式广播了。

注意:

  1. 在Android 7.0(API级别24)开始系统对广播作了一些限制:
    1. 不能发送 ACTION_NEW_PICTUREACTION_NEW_VIDEO广播。
    2. 必须使用registerReceiver(BroadcastReceiver,IntentFilter)注册CONNECTIVITY_ACTION广播。使用静态声明接收者无效。
  2. 在Android 8.0(API级别26)开始系统对清单声明的接收器的限制进一步的加强了,除了少部分隐式广播不限制外,其余的全部隐式广播均没法经过 AndroidManifest.xml中声明,不过能够经过显示调用。官方推荐使用动态注册对广播进行注册。
  3. 在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短信应用须要接收这些广播"

复制代码

参考连接

panzeyong.com/2017/05/21/…

juejin.im/post/5aefd2…

developer.android.com/guide/compo…

本文demo 连接 github.com/tao11122233…

相关文章
相关标签/搜索