广播机制简介
Android中的每个应用程序都可以对自己感兴趣的⼴播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。
Android提供了⼀套完整的API,允许应用程序自由地发送和接收广播。
接收广播的方法:BroadcastReceiver
分类
- 标准广播:是⼀种完全异步执行的广播,在广播发出之后,所有的
BroadcastReceiver
几乎会在同⼀时刻收到这条广播消息,因此它们之间没有任何先后顺序可⾔。这种⼴播的效率会比较⾼,但同时也意味着它是无法被截断的。 - 有序广播:则是⼀种同步执行的广播,在广播发出之后,同⼀时刻只会有⼀个
BroadcastReceiver
能够收到这条广播消息,当这个BroadcastReceiver
中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver
是有先后顺序的,优先级高的BroadcastReceiver
就可以先收到广播消息,并且前面的BroadcastReceiver
还可以截断正在传递的广播,这样后面的BroadcastReceiver
就无法收到广播消息了。
接收系统广播
注册BroadcastReceiver的方法:
- 动态注册:代码中注册,可以自由的控制注册与注销,但只能在程序启动之后才能接受广播。
- 静态注册:在
AndroidManifest.xml
中注册,不允许接收隐式广播
动态注册监听时间变化
新建⼀个继承于BroadcastReceiver
的类,并重写父类的onReceive()
方法。
当有广播到来时,onReceive()
方法就会得到执行,具体的逻辑就可以在这个方法中处理。
class MainActivity : AppCompatActivity() {
private lateinit var mainbinding: ActivityMainBinding
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//使用ViewBinding的标准写法
mainbinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainbinding.root)
//在action中设定想要接受的广播类型
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.TIME_TICK") //时间变化时广播的值为“”
timeChangeReceiver = TimeChangeReceiver() //创建实例
registerReceiver(timeChangeReceiver,intentFilter) //进行注册
}
override fun onDestroy() {
super.onDestroy()
//动态注册的BroadcastReceiver最后一定要取消注册
unregisterReceiver(timeChangeReceiver)
}
inner class TimeChangeReceiver : BroadcastReceiver(){ //内部类
override fun onReceive(context: Context?, intent: Intent?) { //接收到广播后执行
Toast.makeText(context,"Time has changed",Toast.LENGTH_SHORT).show()
}
}
}
快捷方式创建BroadcasrReceiver
快速创建BroadcasrReceiver
类
弹出窗口:
静态注册实现开机启动
用上述方式新建BootCompleteReceiver
类
class BootCompleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show()
}
}
在AndroidManifest.xml
中注册
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
上述的快捷方式会帮助你自动注册
紫色箭头指向的<uses-permission>
标签用于声明权限
橙色箭头指向的<intent-filter>
标签用于声明相应的action
注意
不要在onReceive()
方法中添加过多的逻辑或者进行任何的耗时操作
BroadcastReceiver中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序会出现错误。
发送自定义广播
标准广播
首先需要一个BroadcastReceiver
来接受这个广播
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"receiver in MyBroadCastReceiver", Toast.LENGTH_SHORT).show()
}
}
在AndroidManifest.xml
中注册,并声明接受广播的值
在activity_main.xml
中添加一个button
用于作为发送代码的触发点
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="send BroadCast"
/>
在MainActivity
中设置点击事件
mainbinding.button.setOnClickListener {
val intent = Intent("com.example.broadcasttestkt.MY_BROADCAST")
intent.setPackage(packageName) //传入当前应用程序的包名
sendBroadcast(intent) //发送标准广播
}
注意
在Android 8.0系统之后,静态注册的BroadcastReceiver
是无法接收隐式广播的,而默认情况下我们发出的自定义广播恰恰都是隐式广播。因此这里⼀定要调用setPackage()
方法,指定这条广播是发送给哪个应用程序的,从而让它变成⼀条显式广播,否则静态注册的BroadcastReceiver
将无法接收到这条广播。
标准广播的工作流程实践
首先需要再新建一个BroadcastReceiver
来接受这个广播
class AnotherBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"receiver in MyBroadCastReceiver", Toast.LENGTH_SHORT).show()
}
}
在AndroidManifest.xml
中注册,并声明接受广播的值
再点击button
时,会分别弹出两次Toast
可以说明在标准广播发出后,所有的BroadcastReceiver
几乎会在同时接受到这个广播消息
有序广播
将标准广播改为有序广播只需要改动一行代码
mainbinding.button.setOnClickListener {
val intent = Intent("com.example.broadcasttestkt.MY_BROADCAST")
intent.setPackage(packageName)
//sendBroadcast(intent) //发送标准广播
sendOrderedBroadcast(intent,null) //发送有序广播 第二个参数是权限相关字符串
}
在AndroidManifest.xml
中设置接受广播的优先级,优先级高的先接收
在优先级高的MyBroadcastReceiver
中设置广播截断
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context,"receiver in MyBroadCastReceiver", Toast.LENGTH_SHORT).show()
abortBroadcast() //广播截断
}
}
再次点击button
时就只有只弹出一次消息提示了
最佳实践:强制下线功能
首先创建ActivityCollector
类,用于管理所有的Activity
object ActivityCollector {
private val activities = ArrayList<Activity>()
fun addActivity(activity: Activity){
activities.add(activity)
}
fun removeActivity(activity: Activity){
activities.remove(activity)
}
fun finishAll(){
for (activity in activities){
if (!activity.isFinishing){
activity.finish()
}
}
activities.clear()
}
}
创建BaseActivtiy
类作为所有Activity
的父类
open class BaseActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
}
编写activity_login.xml
作为登录界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".LoginActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Account:" />
<EditText
android:id="@+id/accountEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Password:" />
<EditText
android:id="@+id/passwordEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="200dp"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:text="login" />
</LinearLayout>
在LoginActivity
中编写登录逻辑
class LoginActivity : BaseActivity() {
private lateinit var loginBinding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginBinding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(loginBinding.root)
loginBinding.login.setOnClickListener {
val account = loginBinding.accountEdit.text.toString() //获取用户输入用户名
val password = loginBinding.passwordEdit.text.toString() //获取用户输入密码
if (account == "admin" && password == "123456"){
//如果用户名密码正确,使用intent跳转到MainAvtivity
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
finish()
}else{
//用户名密码错误提示
Toast.makeText(this,"account or password is invalid",Toast.LENGTH_SHORT).show()
}
}
}
}
在activity_main.xml
中添加一个button
,并在MainActivity
中编写点击事件
class MainActivity : BaseActivity() {
private lateinit var mainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
mainBinding.forceOffline.setOnClickListener {
//点击按钮,发送广播
val intent = Intent("com.example.broadcastbestpractices.FORCE_OFFLINE")
sendBroadcast(intent)
}
}
}
BroadcastReceiver
中需要弹出⼀个对话框来阻塞用户的正常操作,但如果创建的是⼀个静态注册的BroadcastReceiver
,是没有办法在onReceive()
方法里弹出对话框这样的UI控件的,而也不可能在每个Activity
中都注册⼀个动态的BroadcastReceiver
。
所以解决方案是:BaseActivity
中动态注册⼀个BroadcastReceiver
就可以了,因为所有的Activity
都继承自BaseActivity
。
open class BaseActivity : AppCompatActivity(){
lateinit var receiver: ForceOfflineReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCollector.addActivity(this)
}
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.broadcastbestpractices.FORCE_OFFLINE")
receiver = ForceOfflineReceiver()
registerReceiver(receiver,intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
inner class ForceOfflineReceiver : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
AlertDialog.Builder(context!!).apply { //构建对话框
setTitle("Warning")
setMessage("You are forced to be offline.Please try to login again.")
setCancelable(false) //对话框设置为不可取消
setPositiveButton("OK"){ _, _ ->
//注册确定按钮 销毁所有Activity并重新启动LoginActivity
ActivityCollector.finishAll()
val restartIntent = Intent(context,LoginActivity::class.java)
context.startActivity(restartIntent)
}
show()
}
}
}
}