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后返回登录页面