赞
踩
目录
广播是个好东西,我们的程序可以发送广播和接受广播,以通知别的程序一些消息,或接受来自别的程序的消息。
广播分为两种广播:
标准广播
这是一种异步的广播。在广播发出去之后,所有的对应的broadcast Receiver都能接收到这个广播消息。这样的广播效率较高,但一旦发送无法拦截。
有序广播
顾名思义,这种广播的发送是有顺序的,是一种同步的广播。按照权重收到广播的顺序分先后。并且先收到广播的程序能够截断这条广播,使后面的receiver都无法接收到。
而广播的接受也有两种方式:
动态注册
这样的方式是我们可以在程序任意时刻注册或注销广播接收器。比如我们在Activity的onResume()里注册广播接收器,onPause()里注销广播接收器。这样的好处在于非常地灵活,局限在于必须要Activity启动了才能收到。
静态注册
我们可以自己写一个类继承于BroadcastReceiver,在AndroidManifest.xml里去注册这个广播接收器,并指定接受的广播类型。
先来尝试用动态注册的方式来接收一个系统发出的广播吧。
新建一个Activity,在里面创建一个内部类:
- inner class TimeChangeReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- Toast.makeText(context, "the time has changed!", Toast.LENGTH_SHORT).show()
- }
- }
这个类继承了BroadcastReceiver,它就是一个广播接收器了。然后重写onReceive()方法,在里面处理一下收到广播时要处理的逻辑。
随后我们在onCreate()里动态注册一下这个广播接收器.
- lateinit var timeChangeReceiver: TimeChangeReceiver
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_dynamic_broadcast)
- timeChangeReceiver = TimeChangeReceiver()
- val intentFilter = IntentFilter()
- // 系统自带的时间改变的广播
- intentFilter.addAction("android.intent.action.TIME_TICK")
- registerReceiver(timeChangeReceiver, intentFilter)
- }
这里调用了registerReceiver()来注册广播接收器。第一个参数是指定广播接收器,第二个参数是一个IntentFilter,用来指定接受哪些广播。android.intent.action.TIME_TICK是Android系统自带的广播,当时间更改时(按分钟)就会发出这条广播。
记住,广播注册了还得注销
- // 记得要注销掉接收器
- override fun onDestroy() {
- super.onDestroy()
- unregisterReceiver(timeChangeReceiver)
- }
现在运行这个程序,每隔一分钟就会弹出一次提示。
像前面的动态注册,如果Activity不启动,那么广播接收器就得不到注册。但是静态注册就没有这样的限制了。
这里我们可以在创建一个广播接收器BroadcastReceiver。
Exported表示是否允许接受本程序以外的广播
Enabled表示这个Receiver是否启动。
创建好后,同样只是要我们重新onReceiv()方法。但是,在Androidmanifest.xml里,Android Studio已经帮我们注册好了这个Receiver。
- <receiver
- android:name=".MyBroadcastReceiver"
- android:enabled="true"
- android:exported="true">
- </receiver>
那我们指定这个Receiver接收哪个广播的方法,也是在Adroidmanifest.xml里去定义。
- <receiver
- android:name=".MyBroadcastReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter >
- <action android:name="com.bingbin.MY_BROADCAST" />
- </intent-filter>
- </receiver>
这里我们定义了会接收com.bingbin.MY_BROADCAST这个广播,那现在我们要去实现发送一个广播。
新建一个Activity,在里面加入一个Button用于发送广播。
- btn_sendBroadcast.setOnClickListener {
- val intent = Intent("com.bingbin.MY_BROADCAST")
- intent.setPackage(packageName)
- sendBroadcast(intent)
- }
发送广播的方法是sendBoradcast(),里面需要传入一个Intent().而创建时我们就指定了这个广播发送的是com.bingbin.MY_BROADCAST。
这里说明一下为什么有intent.setPackage(packageName)这一行:隐式广播指那些没有指定发送程序的广播,比如开机广播、时间改变广播。静态注册的Receiver本来是能接收这些广播的,但是这样会导致有的程序会恶意接收广播来消耗资源,所以Android现在已经禁止静态注册的Receiver接收隐式广播了。所以这里我们需要指定同包名下的程序才能接收到这个广播。
于是点击按钮后,就会出现提示信息:
为了验证标准广播,这里我们再新建另一个BroadcastReceiver来同样接收com.bingbin.MY_BROADCAST这个广播
- class AnotherReceiver : BroadcastReceiver() {
-
- override fun onReceive(context: Context, intent: Intent) {
- Toast.makeText(context, "another receiver also has received my broadcast",
- Toast.LENGTH_SHORT).show()
- }
- }
- <receiver
- android:name=".AnotherReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter >
- <action android:name="com.bingbin.MY_BROADCAST" />
- </intent-filter>
- </receiver>
此时再按按钮,就会出现两条提示信息。
发送标准广播的方法是sendBroadcast(),而发送有序广播的方法是sendOrderBoradcast().
我们再添加一个按钮来发送一条有序广播。
- btn_sendOrderBroadcast.setOnClickListener {
- val intent = Intent("com.bingbin.MY_BROADCAST")
- intent.setPackage(packageName)
- sendOrderedBroadcast(intent, null)
- }
第二个参数设置为null即可。
既然是有序广播,那顺序该如果决定?我们在AndroidManifest.xml的Receiver注册的<intent-filter>标签里决定权重即可。
- <receiver
- android:name=".AnotherReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter android:priority="10">
- <action android:name="com.bingbin.MY_BROADCAST" />
- </intent-filter>
- </receiver>
- <receiver
- android:name=".MyBroadcastReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter android:priority="100">
- <action android:name="com.bingbin.MY_BROADCAST" />
- </intent-filter>
- </receiver>
这样,MyBroadcastReceiver就会先收到广播,AnotherReceiver后收到广播。
如果在MyBroadcastReceiver收到广播的逻辑里调用abortBroadcast()方法,就可以截断这条广播,使AnotherReceiver收不到这条广播。
这样,就只会弹出MyBroadcastReceiver的消息了。
前面用过的系统的android.intent.action.TIME_TICK广播是标准广播还是有序广播?如何给动态注册的BroadcastReceiver设置优先级?
一个BroadcastReceiver可以接收多个广播吗?如果不可以,如果有一个需求是一段时间内收到两个不同的指定广播才会触发一个逻辑,那该如何实现?
静态注册的Receiver的生命周期?如果连续收到数次广播,是每次都是一个新的Receiver,还是同一个实例?
首先因为android.intent.action.TIME_TICK是一个隐式的广播,我们不能用静态注册的方式来测试,只能使用动态注册BroadcastReceiver。
那我们就在一个类里写两个内部BroadcastReceiver,给他们设置优先级,并且优先级高的BroadcastReceiver尝试去截断广播,看优先级低的是否还会接收到。
这里也就顺带解决了如何给动态注册的BroadcastReceiver设置优先级的问题。我们在注册广播时需要传入IntentFilter()参数,而IntentFilter里就可以设置优先级。
- class DynamicBroadcastActivity : AppCompatActivity() {
-
- lateinit var timeChangeReceiver: TimeChangeReceiver
- lateinit var anotherReceiver : AnotherTimeChangeReceiver
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_dynamic_broadcast)
- timeChangeReceiver = TimeChangeReceiver()
- val filter1 = IntentFilter()
- // 系统自带的时间改变的广播
- filter1.addAction("android.intent.action.TIME_TICK")
- filter1.priority = 100
- registerReceiver(timeChangeReceiver, filter1)
-
- anotherReceiver = AnotherTimeChangeReceiver()
- val filter2 = IntentFilter()
- // 系统自带的时间改变的广播
- filter2.addAction("android.intent.action.TIME_TICK")
- filter2.priority = 10
- registerReceiver(anotherReceiver, filter2)
- }
-
- // 记得要注销掉接收器
- override fun onDestroy() {
- super.onDestroy()
- unregisterReceiver(timeChangeReceiver)
- unregisterReceiver(anotherReceiver)
- }
-
- inner class TimeChangeReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- Toast.makeText(context, "Time has changed!", Toast.LENGTH_SHORT).show()
- // 尝试拦截
- abortBroadcast()
- }
- }
-
- inner class AnotherTimeChangeReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- Toast.makeText(context, "AnotherTimeChangeReceiver has received the broadcast",
- Toast.LENGTH_SHORT).show()
- }
- }
- }
测试后发现,AnotherTimeChangeReceiver依然收到了广播。说明了这个系统广播是一个标准广播。
想来也是,如果系统的广播是一个有序广播,那么恶意程序通过注册一个优先级很高的BroadcastReceiver去拦截,很可能就会出现问题。
要测试这个问题,我们先定义一个可以发送两个广播的按钮。
- btn_sendTwoBroadcast.setOnClickListener {
- val broad1 = Intent("com.bingbin.BROADCAST1")
- broad1.setPackage(packageName)
- val broad2 = Intent("com.bingbin.BROADCAST2")
- broad2.setPackage(packageName)
- sendBroadcast(broad1)
- sendBroadcast(broad2)
- }
然后尝试在静态注册的Receiver里接收两个广播。
- <receiver
- android:name=".StrongReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter>
- <action android:name="com.bingbin.BROADCAST1"/>
- <action android:name="com.bingbin.BROADCAST2"/>
- </intent-filter>
- </receiver>
再添加上处理的逻辑
- class StrongReceiver : BroadcastReceiver() {
-
- var i = 0
-
- override fun onReceive(context: Context, intent: Intent) {
- Toast.makeText(context, "I have received a broadcast $i", Toast.LENGTH_SHORT).show()
- i += 1
- }
- }
为了分辨调用了几次,我加了一个全局变量。
查看了一下日志
发现输出了两次,但是i并没有变化。这就有点奇怪了。
后来我又把i放到了一个静态类里
发现输出就比较正确了。
所以这里可以判定,一个BroadcastReceiver是可以指定接收多个广播的。
其实从第二个问题的实践里我们就可以猜出,每次接收广播的Receiver应该都是一个新的实例。
我们再验证一下
- class MyBroadcastReceiver : BroadcastReceiver() {
-
- init {
- Log.d("MyBroadcastReceiver", "创建了一个新的实例")
- }
-
- override fun onReceive(context: Context, intent: Intent) {
- Toast.makeText(context, "I have received my own broadcast!",
- Toast.LENGTH_SHORT).show()
- }
- }
这里我在MyBroadcastReceiver的构造函数里加了一句日志输出。
会发现,每一次接收到广播的时候,静态注册的Receiver都会创建一个新的实例。
那问题就来了,那之前创建的实例会一直存在于内存当中吗?它什么时候被销毁呢?如果不销毁会不会导致OOM呢?
然后我在onReceive()源码里找到了这样一句注释
If this BroadcastReceiver was launched through a <receiver> tag, then the object is no longer alive after returning from this function. This means you should not perform any operations that return a result to you asynchronously.
所以我们能看到,如果是静态注册的Receiver,在调用完onReceive()过后,就会被销毁了。也就不存在内存风险了。
我们动态注册的时候,也一定要注意主动地调用unregisterReceiver()来注销Recevier,否则可能会有内存泄漏的危险。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。