Android 四大组件(三) —— BroadcastReceiver 知识体系

BroadcastReceiver 简介

广播用于发送通知消息,应用程序可以选择接收自己感兴趣的广播,广播的接收方式为注册 BroadcastReceiver,然后在其 onReceive 方法中处理接收到的广播。

广播分为标准广播有序广播。两者的区别在于:标准广播发出后,所有的 BroadcastReceiver 会同时收到,而有序广播会按照 BroadcastReceiver 的优先级或注册顺序依次接收、依次处理,并且前面的 BroadcastReceiver 可以中断广播的继续传播。

广播还可以按照另一个维度划分为显式广播隐式广播,隐式广播指的是没有具体指定发送给哪个应用程序的广播。系统广播大多数都是隐式广播。

BroadcastReceiver 有两种注册方式:动态注册静态注册。动态注册是指在代码中注册 BroadcastReceiver,静态注册指的是在 AndroidManifest 中注册 BroadcastReceiver。动态注册的 BroadcastReceiver 只有在程序运行时,代码中调用 registerReceiver 注册了之后才能收到广播,而静态注册的广播即使程序没有启动也能接收到广播。

由于静态注册经常被滥用,所以自 Android 8.0 以后,所有的隐式广播都不允许使用静态注册的方式来接收了。大多数系统广播属于隐式广播,只有少数特殊的系统广播仍然允许使用静态注册的方式来接收。这些特殊的系统广播在这里可以找到:https://developer.android.google.cn/guide/components/broadcast-exceptions.html

一、动态注册

系统每隔一分钟就会发出一条 Intent.ACTION_TIME_TICK 广播,这个广播的意思是系统时间发生了变化。接下来我们就用动态注册 BroadcastReceiver 的方式来接收它。

新建 TimeChangeBroadcastReceiver:

class TimeChangeBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("~~~", "Time has changed")
    }
}

MainActivity 中动态注册:

class MainActivity : AppCompatActivity() {

    private val receiver by lazy { TimeChangeBroadcastReceiver() }
    private val intentFilter by lazy {
        IntentFilter().apply {
            addAction(Intent.ACTION_TIME_TICK)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(receiver, intentFilter)
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }
}

使用registerReceiverunregisterReceiver 就完成了注册和取消注册,注册时传入的 intentFilter 用来指定接收广播的类型。最多等待一分钟就能在控制台看到 Log:

~~~: Time has changed

监听其他系统广播的做法也是类似的,完整的系统广播列表可以在 Intent 类中查看,也可以在 SDK 中查看,路径为:

SDK 路径/platforms/任意 android api 版本/data/broadcast_actions.txt

二、静态注册

在上文的特殊系统广播列表中,可以看到 Intent.ACTION_BOOT_COMPLETED 是可以静态注册的,这个广播是指系统开机完成。接下来我们就用静态注册 BroadcastReceiver 的方式来接收它。

新建 BootCompleteBroadcastReceiver:

class BootCompleteBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("~~~", "boot complete")
    }
}

在 AndroidManifest 中注册这个 Receiver:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<application ...>
    ...
    <receiver android:name=".BootCompleteBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
    </receiver>
</application>

需要注意的是,接收此静态广播必须声明 RECEIVE_BOOT_COMPLETED 权限,这是为了让用户知道我们需要接收开机完成的广播,Android 希望所有应用都做到对用户透明。安装 app 后重启手机,待系统系统完成后,就可以在控制台看到 Log:

~~~: boot complete

三、发送自定义广播

接下来我们来发送一条自定义的广播,并接收它。

先新建一个类来接收我们的自定义广播,新建 MyBroadcastReceiver:

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        Log.d("~~~", "receive my broadcast")
    }
}

静态注册一下:

<application ...>
    ...
    <receiver android:name=".MyBroadcastReceiver">
            <intent-filter>
                <action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
            </intent-filter>
    </receiver>
</application>

然后我们可以在任何地方发送此广播进行测试,比如 MainActivity 启动时发送:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
            setPackage(packageName)
        })
    }
}

此时打开 MainActivity,就可以在控制台看到 Log:

~~~: receive my broadcast

注意这里我们使用了 setPackage 方法,传入了我们这个应用的包名,表示这个广播指定发送给我们的这个应用程序,只有这样它才是一个显式广播。上文已经说过,Android 8.0 以后,隐式广播是无法通过静态注册接收到的。

本例也可以使用动态注册的方式,只需删除 AndroidManifest 中静态注册的代码,再修改 MainActivity 如下:

class MainActivity : AppCompatActivity() {
    private val receiver by lazy { MyBroadcastReceiver() }
    private val intentFilter by lazy {
        IntentFilter().apply {
            addAction("com.wkxjc.myapplication.MY_BROADCAST")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(receiver, intentFilter)
        sendBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST"))
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }
}

这样就完成了动态注册,动态注册时我们就不需要使用 setPackage 方法了,运行效果和静态注册一样。

四、有序广播

intentFilter 可以设置 priority 属性,表示 BroadcastReceiver 的优先级。发送有序广播时,优先级高的 BroadcastReceiver 会先接收到,处理后再传递给优先级第二高的 BroadcastReceiver,以此类推。priority 默认是 0,官方规定 priority 的范围应该在 (-1000, 1000) 区间,因为系统会使用优先级 1000 这个值,使系统的一些广播被最高优先级接收,我们不应该打破这个规定。但编译器不会检查 priority 的值是否在区间内,所以这个规定只是一个契约。

静态注册的 BroadcastReceiver 通过添加 android:priority 设置 priority 属性,例如:

<receiver android:name=".HighPriorityBroadcastReceiver">
    <intent-filter android:priority="100">
        <action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
    </intent-filter>
</receiver>

动态注册的 BroadcastReceiver 通过给 intentFilter 设置,例如:

private val intentFilter by lazy {
    IntentFilter().apply {
        addAction("com.wkxjc.myapplication.MY_BROADCAST")
        priority = 100
    }
}

如果两个 BroadcastReceiver 优先级相同,则先注册的 BroadcastReceiver 会先接收到。对于静态注册,就受 AndroidManifest 中注册的顺序影响,对于动态注册,就受代码中调用 registerReceiver 的顺序影响。

有序广播的 BroadcastReceiver 中,可以调用 abortBroadcast 函数中断此广播。发送有序广播的方式也很简单。调用 sendOrderedBroadcast(yourIntent, null)即可,这个函数的第二个参数是与权限有关的,我们暂时用不到,传递 null 即可。

接下来我们来测试验证一下。

新建 HighPriorityBroadcastReceiver 类:

class HighPriorityBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("high", "receive")
        abortBroadcast()
    }
}

在这个类中,我们收到广播后,打印日志,然后将广播中断。

新建 LowPriorityBroadcastReceiver 类:

class LowPriorityBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("low", "receive")
    }
}

这个类中仅仅打印一行日志。

然后静态注册一下这两个 BroadcastReceiver :

<application ...>
    ...
    <receiver android:name=".LowPriorityBroadcastReceiver">
        <intent-filter android:priority="0">
            <action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
        </intent-filter>
    </receiver>
    <receiver android:name=".HighPriorityBroadcastReceiver">
        <intent-filter android:priority="100">
            <action android:name="com.wkxjc.myapplication.MY_BROADCAST" />
        </intent-filter>
    </receiver>
</application>

我们将两个 BroadcastReceiver 的 priority 分别设置成了 100 和 0 。这里我故意让 LowPriorityBroadcastReceiver 先注册,以验证 priority 真的能改变接收次序。

在 MainActivity 中发送显式广播:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendOrderedBroadcast(Intent("com.wkxjc.myapplication.MY_BROADCAST").apply {
            setPackage(packageName)
        }, null)
    }
}

运行程序,控制台 Log 如下:

high: receive

如果将 AndroidManifest 中,两个 BroadcastReceiver 的 priority 属性都去掉,则控制台会输出:

low: receive
high: receive

这是因为 LowPriorityBroadcastReceiver 是先注册的,并且 LowPriorityBroadcastReceiver 中没有中断广播继续传播。所以我们验证了 priority 确实能改变 BroadcastReceiver 的接收次序。并且 abortBroadcast 确实能中断有序广播的传播。

如果需要在广播中携带数据,只需在发送广播时将数据传到 intent 中,再从 BroadcastReceiver 的 onReceive 函数中的 intent 参数中取出即可。

参考文章

《第一行代码》(第三版)- 第 6 章 全局大喇叭,详解广播机制

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值