《第一行代码 第3版》学习笔记——第六章 广播

1 概述

广播概念在计算机网络中就有,在一个IP网络范围中,最大的IP地址通常是被保留作为广播地址来使用的。
广播的含义就是一个广播发出,范围内的应用程序都能够收到该广播。
Android中的每个应用程序都可以对自己感兴趣的广播进行注册,注册后当广播发出时,就能够收到,可能是系统广播,也可能是其他应用程序的广播。
广播接收的应用中需要有类来继承BroadcastReceiver类。
广播按照分类可以分为两种:

  • 标准广播:完全异步执行的广播,广播发出后,所有相关的BroadcastReceiver能够几乎在同一时间收到广播消息,没有先后顺序可言。这种广播效率较高,无法被截断。
  • 有序广播:是一种同步执行的广播,同一时刻只会有一个BroadcastReceiver收到广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。

2 接收系统广播

2.1 动态注册监听时间变化

BroadcastReceiver属于Android四大组件之一,使用时需要注册,可以分为两种方式注册,在代码中注册是动态注册,在AndroidManifest.xml中注册是静态注册。

class MainActivity : ComponentActivity() {
    lateinit var timeChangeReceiver: BroadcastReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        timeChangeReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
                val date = Date(System.currentTimeMillis())
                val dateStr = dateFormat.format(date)
                Toast.makeText(context, "Now: $dateStr", Toast.LENGTH_SHORT)
                    .show()
            }
        }
        val intentFilter = IntentFilter("android.intent.action.TIME_TICK")
        registerReceiver(timeChangeReceiver, intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeChangeReceiver)
    }
}

这里监听系统广播TIME_TICK,每一分钟,系统会发送一次该广播。在onCreate中注册,在onDestroy中注销,动态注册要记得注销。
在这里插入图片描述
Android提供了多种系统广播,查看完整的系统广播列表,可以到以下路径查看:
<Android SDK>/platforms/<api version>/data/broadcast_actions.txt

2.2 静态注册实现开机启动

动态注册的BroadcastReceiver可以自由的控制注册与注销,灵活性上有很大的优势,但是必须程序启动后才能够接收广播。静态注册则可以在程序未启动时注册广播。

class BootCompleteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "BootCompleteReceiver: Boot completed", Toast.LENGTH_LONG).show()
    }
}

AndroidManifest.xml

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
<receiver
    android:name=".BootCompleteReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

静态注册并声明对应权限
在这里插入图片描述
当系统启动完成时,会收到系统广播
需要注意的是,不要在onReceive方法中添加过多的逻辑或者进行任何耗时操作,因为BroadcastReceiver中是不允许开启线程的,当onReceive方法运行了较长时间而没有结束时,程序会出现错误。

3 发送自定义广播

3.1 发送标准广播

自定义广播,并发送广播。自定义就是定义广播的action

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BroadcastDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier,
                    color = MaterialTheme.colorScheme.background
                ) {
                    Button(
                        modifier = Modifier
                            .height(100.dp)
                            .width(300.dp),
                        onClick = {
                            val intent = Intent("com.xy.broadcastdemo.MY_BROADCAST")
                            intent.`package` = packageName
                            sendBroadcast(intent)
                        }
                    ) {
                        Text(text = "send broadcast")
                    }
                }
            }
        }
    }
}

在Andorid 8之后,静态注册的BroadcastReceiver无法接收隐式广播,需要调用setPackage方法,指定这条广播发送给哪个应用程序,从而让它变成一条显示广播。
接收自定义广播
MyReceiver

class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "received my broadcast", Toast.LENGTH_LONG).show()
    }
}

AndroidManifest.xml

<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.xy.broadcastdemo.MY_BROADCAST" />
    </intent-filter>
</receiver>

当点击按钮发送广播时,能够收到自定义广播
在这里插入图片描述

3.2 发送有序广播

发送有序广播使用sendOrderedBroadcast进行发送,各个接收者的顺序在静态注册时设定。
发送广播

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BroadcastDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier,
                    color = MaterialTheme.colorScheme.background
                ) {
                    Button(
                        modifier = Modifier
                            .height(100.dp)
                            .width(300.dp),
                        onClick = {
                            val intent = Intent("com.xy.broadcastdemo.MY_BROADCAST")
                            intent.`package` = packageName
                            sendOrderedBroadcast(intent, null)
                        }
                    ) {
                        Text(text = "send broadcast")
                    }
                }
            }
        }
    }
}

使用sendOrderedBroadcast发送,第一个参数是intent,第二个参数是一个权限相关的参数。
AndroidManifest.xml

<receiver
    android:name=".MyReceiver2"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.xy.broadcastdemo.MY_BROADCAST" />
    </intent-filter>
</receiver>
<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="100">
        <action android:name="com.xy.broadcastdemo.MY_BROADCAST" />
    </intent-filter>
</receiver>

将MyReceiver的priority设置成100,保证MyReceiver首先接收到广播,MyReceiver处理完成之后才会转发给MyReceiver2。

class MyReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
        Toast.makeText(context, "received my broadcast", Toast.LENGTH_LONG).show()
        abortBroadcast()
    }
}

这里在MyReceiver中调用abortBroadcast()方法将广播截断,MyReceiver中处理完成之后,广播不会继续传播。

4 广播最佳实践:实现强制下线功能

强制下线功能,收到广播后,需要先关闭所有的Activity,然后回到登录页面
ActivityCollector

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()
    }
}

用于Activity的管理工作
BaseActivity

open class BaseActivity : ComponentActivity() {
    val TAG = "BaseActivity"
    lateinit var receiver: ForceLineReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)
    }

    override fun onResume() {
        super.onResume()
        val intentFilter = IntentFilter()
        intentFilter.addAction("com.example.broadcastdemo.FORCE_OFFLINE")
        receiver = ForceLineReceiver()
        registerReceiver(receiver, intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)
    }

    inner class ForceLineReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            Log.i(TAG, "onReceive: ")
            AlertDialog.Builder(context).apply {
                setTitle("Warning")
                setMessage("You are forced to be offline, Please try to login again")
                setCancelable(false)
                setPositiveButton("OK") { _, _ ->
                    ActivityCollector.finishAll()
                    val i = Intent(context, LogInActivity::class.java)
                    context.startActivity(i)
                }
                show()
            }
        }
    }
}

所有Activity的基类,应用中的所有Activity都继承该类,在该类中可以实现Activity的集中管理,可以实现强制下线的广播处理。当有强制下线广播发出时,基类中收到该广播,能够实现关闭所有Activity,并返回登录页的操作。无需每个Activity都去自己调用finish而退出Activity栈。
LogInActivity

class LogInActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BroadcastDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val clickLogIn: (String, String) -> Unit = { username, passwd ->
                        if (username == "admin" && passwd == "123") {
                            val intent = Intent(this, SendBroadcastPage::class.java)
                            startActivity(intent)
                            finish()
                        } else {
                            Toast.makeText(this, "invalid username or passwd", Toast.LENGTH_SHORT)
                                .show()
                        }
                    }
                    LoginPage(Modifier, clickLogIn)
                }
            }
        }
    }
}

@Composable
fun LoginPage(
    modifier: Modifier = Modifier,
    onLogInClicked: (String, String) -> Unit,
) {
    var username by remember {
        mutableStateOf("")
    }
    var passwd by remember {
        mutableStateOf("")
    }
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Row(
            modifier = modifier
                .fillMaxWidth()
                .padding(10.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "User:",
                color = MaterialTheme.colorScheme.primary,
                modifier = Modifier.width(70.dp),
                textAlign = TextAlign.Center
            )
            TextField(
                value = username,
                onValueChange = { username = it },
                placeholder = { Text(text = "username") },
                modifier = modifier.height(56.dp)
            )
        }
        Row(
            modifier = modifier
                .fillMaxWidth()
                .padding(10.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Passwd:",
                color = MaterialTheme.colorScheme.primary,
                modifier = modifier.width(70.dp),
                textAlign = TextAlign.Center
            )
            TextField(
                value = passwd,
                onValueChange = { passwd = it },
                placeholder = { Text(text = "passwd") },
                modifier = modifier.height(56.dp)
            )
        }
        Button(
            onClick = { onLogInClicked(username, passwd) },
            modifier = Modifier
                .padding(20.dp)
                .width(180.dp),
            shape = RoundedCornerShape(10.dp)
        ) {
            Text(text = "LogIn")
        }
    }
}

登录页面,有username和passwd两个输入输入框,有一个login按钮,当点击login按钮时,会校验username和passwd是否为admin和123,是则跳转到发送强制下线广播的页面。
SendBroadcastPage

class SendBroadcastPage : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)
        setContent {
            BroadcastDemoTheme {
                Surface(
                    modifier = Modifier.fillMaxSize()
                ) {
                    Column(
                        verticalArrangement = Arrangement.Top
                    ) {
                        Button(
                            onClick = {
                                val intent = Intent("com.example.broadcastdemo.FORCE_OFFLINE")
                                sendBroadcast(intent)
                            },
                            modifier = Modifier
                                .fillMaxWidth()
                                .wrapContentHeight(),
                            shape = RoundedCornerShape(10.dp)
                        ) {
                            Text(text = "send Broadcast")
                        }
                    }
                }
            }
        }
    }
}

广播发送页面,这里模拟的是登录挤出导致的强制下线功能。正常的强制下线功能理应由服务器触发来发送广播,这里模拟使用按钮触发。点击按钮后,会弹出一个警告框,并会弹出该应用的所有的Activity,并返回登录页面。
下面是效果:
在这里插入图片描述
登录页面
在这里插入图片描述
输入账号密码,点击登录
在这里插入图片描述
跳转发送广播页面,点击发送广播,发送强制下线广播
在这里插入图片描述
弹出警告框,点击ok后返回登录页面
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值