完整代码Gitee地址:kotlin-demo: 15天Kotlin学习计划
第五天学习内容代码:Chapter5
目录
前言
定义
应用场景
知识点1:静态注册广播
知识点2:动态注册广播
知识点3:自定义全局广播
前言
BroadcastReceiver (广播接收器),属于 Android 四大组件之一- 在
Android 开发中,BroadcastReceiver 的应用场景非常多 - 今天,我将详细讲解关于
BroadcastReceiver 的一切相关知识
定义
即 广播,是一个全局的监听器,属于Android 四大组件之一
Android ?广播分为两个角色:广播发送者、广播接收者
应用场景
Android 不同组件间的通信(含 :应用内 / 不同应用之间)- 多线程通信
- 与
Android 系统在特定情况下的通信
?比如今天的例子:时间变化监听、开启启动完成监听、网络变化监听
知识点1:静态注册广播
????????动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。但是它存在着一个缺点,即必须在程序启动之后才能接收广播,因为注册的逻辑是写在onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下也能接收广播呢?这就需要使用静态注册的方式了。
????????由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。
????????静态注册实现开机启动,出了创建内部类,还可以通过Android Studio提供的快捷方式来创建。右击com.example.broadcasttest包→New→Other→Broadcast Receiver,会弹出如图所示的窗口。
?然后修改BootCompleteReceiver中的代码,如下所示:
class MyReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "开机广播", Toast.LENGTH_SHORT).show()
//接收到广播打开当前程序
context.startActivity(context.packageManager.getLaunchIntentForPackage(context.packageName))
}
}
?代码非常简单,我们在onReceive中实现了打开当前应用;
????????另外,静态的BroadcastReceiver一定要在AndroidManifest.xml文件中注册才可以使用。不过,由于我们是使用Android Studio的快捷方式创建的BroadcastReceiver,因此注册这一步已经自动完成了。打开AndroidManifest.xml文件瞧一瞧,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kotlin_demo">
<!-- 接收启动完成 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Kotlindemo">
....
<!-- 静态注册,开机启动广播 -->
<receiver
android:name=".Chapter5.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
自动创建的MyReceiver是无法收到开机广播的,因为我们还需要对AndroidManifest.xml文件进行修改才行,如上所示,运行效果如下:
知识点2:动态注册广播
????????动态广播最好在Activity 的 onResume()注册、onPause()注销,对于动态广播,有注册就必然得有注销,否则会导致内存泄露注册。
????????在代码中调用registerReceiver() 方法,具体代码如下:
override fun onResume() {
super.onResume()
// 监听时间变化,通常一分钟跳一次时间
time = TimeReceiver()
val timeFilter = IntentFilter()
timeFilter.addAction(Intent.ACTION_TIME_TICK)
registerReceiver(time, timeFilter)
// 监听网络变化
netWork = NetworkReceiver()
val networkFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(netWork, networkFilter)
}
override fun onPause() {
super.onPause()
//页面关闭,停止广播
unregisterReceiver(time)
unregisterReceiver(netWork)
}
inner class TimeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "时间被监听", Toast.LENGTH_SHORT).show()
Log.i("TAG", "TimeReceiver: 时间被监听")
}
}
inner class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.i("TAG", "NetworkReceiver: 网络被监听")
val manager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = manager.activeNetworkInfo
// 判断网络情况
if (networkInfo != null && networkInfo.isAvailable) {
// 网络可用时的执行内容
Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show()
} else {
// 网络不可用时的执行内容
Toast.makeText(context, "network connect fail", Toast.LENGTH_SHORT).show()
}
}
}
效果如下:
知识点3:自定义全局广播
? ? ? ? 实现全局通知消息广播,点击确认退出程序的功能。
????????由于BroadcastReceiver中需要弹出一个对话框来阻塞用户的正常操作,但如果创建的是一个静态注册的BroadcastReceiver,是没有办法在onReceive()方法里弹出对话框这样的UI控件的,而我们显然也不可能在每个Activity中都注册一个动态的BroadcastReceiver。那么到底应该怎么办呢?答案其实很明显,只需要在BaseActivity中动态注册一个BroadcastReceiver就可以了,因为所有的Activity都继承自BaseActivity。
先创建一个ActivityCollector类用于管理所有的Activity,代码如下所示:
object ActivityCollector {
private val activityList = ArrayList<Activity>()
//添加activity到集合
fun addActivity(activity: Activity) {
activityList.add(activity)
}
//从集合里面移除
fun removeActivity(activity: Activity){
activityList.remove(activity)
}
//关闭所有activity
fun finishAll(){
for (activity in activityList){
if (!activity.isFinishing){
activity.finish()
}
}
activityList.clear()
}
}
然后创建BaseActivity类作为所有Activity的父类,代码如下所示:
open class BaseActivity : AppCompatActivity() {
private lateinit var text: TextReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i("BaseActivity", javaClass.simpleName)
//添加activity
ActivityCollector.addActivity(this)
}
override fun onResume() {
super.onResume()
text = TextReceiver()
val textFilter = IntentFilter("com.example.kotlin_demo.TextReceiver")
registerReceiver(text, textFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(text)
}
inner class TextReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
AlertDialog.Builder(context)
.setTitle("消息提醒")
.setMessage("这是一条全局广播,点我退出应用")
.setPositiveButton("确定") { _, _ ->
ActivityCollector.finishAll()
}
.setNeutralButton("取消", null)
.create()
.show()
}
}
override fun onDestroy() {
super.onDestroy()
//销毁activity
ActivityCollector.removeActivity(this)
}
}
然后通过一个按钮用于触发自定义广播。如下所示:
//3、在BaseActivity,自定义一条全局广播,推送一条消息
val butSend :AppCompatButton = findViewById(R.id.but_send)
butSend.setOnClickListener {
//任意地方可主动发起广播
val intent = Intent("com.example.kotlin_demo.TextReceiver")
sendBroadcast(intent)
}
运行效果如下:
????????因为我们始终需要保证只有处于栈顶的Activity才能接收到这条强制下线广播,非栈顶的Activity不应该也没必要接收这条广播,所以写在onResume()和onPause()方法里就可以很好地解决这个问题,当一个Activity失去栈顶位置时就会自动取消BroadcastReceiver的注册。
|