提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方,欢迎各位在评论中指出。
文章目录
一、Android的广播机制
Android的广播机制是观察者模式在Android系统中的一种实际应用,它允许应用程序在接收到特定的广播后执行某些操作。其中,发送广播是通过Intent来实现的,接收广播是通过BroadcastReceiver(广播接收器)来实现的。Android广播的类型有两种,他们分别是:① 标准广播 ② 有序广播。
1.1 标准广播
标准广播是一种完全异步执行的广播,在广播发出后所有的BroadcastReceiver几乎都会同时收到这条广播消息。因此这些BroadcastReceiver之间没有先后顺序可言。这种广播的效率很高,但也是无法被截断的。
1.2 有序广播
有序广播则是一种同步执行的广播,在广播发出后,同一时刻只能有一个BroadcastReceiver能够收到这条广播消息。当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver是有先后顺序的,优先级高的BroadcastReceiver会先接收到广播,并且先收到广播的BroadcastReceiver还可以截断正在传递的广播,让后面的BroadcastReceiver无法接收到这条广播消息。
二、广播接收器的注册
顾名思义,BroadcastReceiver(广播接收器)就是用来接收Broadcast(广播)的。
- 注册广播接收器主要有两种方法:①动态注册(代码中注册) ②静态注册(AndroidMainfest.xml中注册)
- 创建广播接收器的方法很简单:创建一个类,让其继承BroadcastReceiver,重写onReceive()方法并在该方法内实现接收广播后的处理逻辑。
2.1 动态注册广播接收器监听时间的变化
动态注册的广播接收器会把接收到的广播保存在内存中。若动态注册的广播接收器在注册后一直存在,那么即使它不再需要接收任何广播,这些广播仍然会被保存在内存中,这很可能会导致内存泄漏。所以在不使用广播接收器的时候需要将其注销,防止发生内存泄露。
以下是动态注册广播接收器的流程:
创建广播接收器 ——> 在onReceive( ) 中添加广播处理逻辑 ——>创建意图过滤器IntentFilter(匹配接收的广播)——> 为IntentFilter添加action动作 ——>初始化广播接收器——>注册广播接收器——> 在不需要使用时注销广播接收器
我们新建一个BroadcastTest项目,并修改MainActivity中的代码:
class MainActivity : AppCompatActivity() {
//延迟初始化 需确保timeChangeReceiver在使用前已经被初始化了
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myIntentFilter = IntentFilter() // 创建意图过滤器(匹配接收的广播)
myIntentFilter.addAction("android.intent.action.TIME_TICK") // 为过滤器添加动作
timeChangeReceiver = TimeChangeReceiver() // 初始化广播接收器
registerReceiver(timeChangeReceiver, myIntentFilter) // 注册广播接收器 让其接收指定广播
}
//界面被销毁
override fun onDestroy() {
super.onDestroy()
/* 广播机制是一种观察者模式 它会把所有接收到的广播保存在内存中
如果一个广播接收器在注册后一直存在 即使它不再需要接收广播
但这些广播仍然会被保存在内存中 这可能会导致内存泄漏
所以需要在不使用广播的时候注销广播接收器 防止发生内存泄露 */
unregisterReceiver(timeChangeReceiver)
}
//创建自定义广播接受器
inner class TimeChangeReceiver : BroadcastReceiver() {
//处理接收到的广播
override fun onReceive(context: Context?, intent: Intent?) {
Toast.makeText(context, "Time has changed!", Toast.LENGTH_SHORT).show()
}
}
}
我们在MainActivity中自定义了一个广播接收器——TimeChangeReceiver,并让其继承自BroadcastReceiver。然后我们重写了父类的onReceive()方法,在onReceive()中我们让TimeChangeReceiver接收到特定广播后弹出一个Toast弹窗。
在onCreate()方法中,我们首先创建了一个意图过滤器IntentFilter,然后为其添加action。该action是一个系统级的广播,当系统时间的分钟发生变化时就会发送该广播。然后我们初始化了广播接收器TimeChangeReceiver,并对其进行注册。在onDestroy()方法中,我们在不使用广播接收器时对其进行注销,以防止发送内存泄露。
动态注册的广播接收器在不使用的时候必须要注销,否则可能会发生内存泄露。
这样每当手机系统分钟发生变化时都会弹出一个Toast弹窗:
2.2 静态注册广播接收器实现开机启动
动态注册广播接收器可以自由的注册和注销,使用起来十分灵活。但是它有一个缺点,那就是必须在应用程序启动以后才可以接收广播,因为广播接收器的注册逻辑是写在onCreate()方法中的。如果我们想在应用程序还没启动的时候就可以接收广播呢?那么就可以借助静态注册广播接收器的方式来实现。
系统级广播大多数都是隐式广播,隐式广播是指没有具体指定要发送给哪个应用程序的广播。由于Android系统的限制,几乎所有的隐式广播都不允许被静态注册的广播接收器来接收了(容易被恶意程序利用进而频繁从后台唤醒)。但还是有极少数广播被允许使用静态注册的方式来接收。例如:android.permission.RECEIVE_BOOT_COMPLETED,当Android系统启动完毕后就会发送这条广播。
这里尝试让我们的应用程序接收Android系统的开机广播,当Android系统启动完毕并发送开机广播后,我们的应用程序会接收该广播并打印一段文字。这次我们让Android Studio帮我们创建BroadcastReceiver,包名——>New——>Other——>BroadcastReceiver——>创建BootCompleteReceiver.kt——>勾选Exported和Enabled
exported属性表示是否允许这个广播接收器接收本程序之外的广播
enabled属性表示是否启用这个广播接收器
BootCompleteReceiver.kt
class BootCompleteReceiver : BroadcastReceiver() {
//广播处理逻辑
override fun onReceive(context: Context, intent: Intent) {
//打印日志
Log.d("BootCompleteReceiver", "Boot has been completed!")
}
}
AndroidManifest.xml
我们可以看到在< application>标签内出现了一个新的 < receiver>标签 ,这就是Android Studio自动帮我们配置好的,所有的静态广播接收器都是在这里进行注册的。我们在 < receiver>标签中添加了一个< intent-filter>意图过滤标签,并在里面声明相应的action。
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
//权限声明
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
· · ·>
//开机广播接收器BootCompleteReceiver
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true" // 是否启用该广播接收器
android:exported="true" //是否允许接收本程序之外的广播
>
//过滤目标广播
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> // 开机广播
</intent-filter>
</receiver>
//主界面
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我们首先在< application >标签上面进行了权限声明,Android系统强制要求敏感权限必须要进行声明,否则会Crash。我们在 < uses-permission >标签中声明了应用程序接收开机广播的权限,然后在广播接收器中配置意图过滤器,并添加我们想要接收的目标广播。可以看到,在我们重启手机后,当Android系统启动完毕后就会执行相应的逻辑打印日志。
需要注意的是:不要在onReceive()方法中添加过多的逻辑或者进行任何耗时(超过10s)的操作,因为BroadcastReceiver中不允许开启线程。当onReceive()方法运行了较长时间而没有结束时,应用程序就会Crash。
三、发送自定义广播
通过上面的学习你已经学会了如何通过BroadcastReceiver来接收系统广播,下面我们来学习一下如何在我们的应用程序中发送自定义广播。前面你已经知道了发送广播是通过Intent来实现,并且广播分为标准广播 和有序广播。接下来就让我们学习一下如何通过Intent来发送标准广播和有序广播吧。
3.1 发送标准广播
为了能够接收到我们发送的广播,我们需要先创建一个广播接收器。通过Android Studio新建一个MyBroadcastReceiver.kt广播接收器:
class MyBroadcastReceiver : BroadcastReceiver() {
//收到广播后的处理逻辑
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "广播已被MyBroadcastReceiver接收!", Toast.LENGTH_SHORT).show()
}
}
这样当MyBroadcastReceiver收到自定义广播后就会弹出一个Toast弹窗。接下来我们在AndroidMainfest.xml中对刚刚创建的MyBroadcastReceiver.kt广播接收器进行注册:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
· · ·
android:theme="@style/AppTheme">
// 自定义广播接收器
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
// 过滤目标广播
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST" /> // 自定义广播
</intent-filter>
</receiver>
// 开机广播接收器
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
· · ·
</activity>
</application>
</manifest>
结下来我们在主界面activity_main.xml中增加一个Button用来发送自定义广播:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/mySendButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送广播" />
</LinearLayout>
在这里我们补充一个知识,以便你更好的理解接下来的代码。隐式广播与显式广播的区别:
- 隐式广播:没有指定要发送给哪个应用程序的广播。注意,在Android 8.0之后的系统中,静态注册的BroadcastReceiver是无法接受除少数特殊的系统广播之外的隐式广播的。
- 显式广播:发送广播时通过setPackage()方法指定了目标应用程序的广播。只有特定包名的应用可以接收到这条广播。
接下来,我们在MainActivity.kt中实现点击按钮发送广播的逻辑。需要注意的是,我们的广播接收器MyBroadcastReceiver是通过静态方法注册的。但是几乎所有的隐式广播都不允许使用静态注册的方式来接收,而默认情况下我们发送的自定义广播都恰恰是隐式的。所以我们必须要通过setPackage()为intent设置包名(限制只有特定包名的应用可以接收到这条广播),这样我们自定义的广播就会变成一条显式广播。
class MainActivity : AppCompatActivity() {
//延迟初始化 需确保timeChangeReceiver在使用前已经被初始化了
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//点击按钮发送广播
mySendButton.setOnClickListener {
//创建意图
val myIntent = Intent("com.example.broadcasttest.MY_BROADCAST")
//显式广播 指定这条广播是发送给哪个应用程序的
myIntent.setPackage(packageName)
//发送广播
sendBroadcast(myIntent)
}
· · ·
}
· · ·
}
运行效果如下:
需要注意的是,由于发送广播是通过Intent来实现的,因此我们还可以在Intent中携带一些数据传递给相应的BroadcastReceiver。如果你的广播需要传递一些数据,你可以使用putExtra()函数来添加额外的数据:
val intent = Intent()
intent.action = "com.example.broadcasttest.MY_BROADCAST"
intent.putExtra("key", "value") // 添加额外的数据
sendBroadcast(intent)
在这个示例中,我们使用putExtra()函数添加了一个键值对。这个额外的数据可以在接收到广播的接收器中通过Intent对象获取。
3.2 发送有序广播
和标准广播不同,有序广播是一种同步执行的广播,并且是可以被截断的。这里我们新建一个AnotherBroadcastReceiver用来接收自定义有序广播:
class AnotherBroadcastReceiver : BroadcastReceiver() {
//收到广播后的处理逻辑
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "广播已被AnotherBroadcastReceiver接收!", Toast.LENGTH_SHORT).show()
}
}
然后我们在AndroidMainfest.xml文件中配置意图过滤器:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
· · ·
android:theme="@style/AppTheme">
<!-- 自定义AnotherBroadcastReceiver广播接收器 -->
<receiver
android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
· · ·
</application>
</manifest>
可以看到AnotherBroadcastReceiver同样接收的是之前自定义的MY_BROADCAST广播。如果现在你重新运行程序后点击Button按钮,会分别弹出两次Toast,这表明我们定义的MyBroadcastReceiver和AnotherBroadcastReceiver都接收到了MY_BROADCAST广播。不过目前为止,我们应用程序发出的都是标准广播。若想发送一个有序广播,则需要将sendBroadcast( )方法——>sendOrderedBroadcast( )方法。
下面是MainActivity.kt的代码:
class MainActivity : AppCompatActivity() {
//延迟初始化 需确保timeChangeReceiver在使用前已经被初始化了
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mySendButton.setOnClickListener {
val myIntent = Intent("com.example.broadcasttest.MY_BROADCAST")
myIntent.setPackage(packageName)
发送标准广播
//sendBroadcast(myIntent)
发送有序广播
sendOrderedBroadcast(myIntent, null)
}
val myIntentFilter = IntentFilter()
myIntentFilter.addAction("android.intent.action.TIME_TICK")
timeChangeReceiver = TimeChangeReceiver()
registerReceiver(timeChangeReceiver, myIntentFilter)
}
//界面被销毁
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(timeChangeReceiver)
}
//创建自定义广播接受器
inner class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Toast.makeText(context, "Time has changed!", Toast.LENGTH_SHORT).show()
}
}
}
可以看到我们只修改了一行代码就实现了标准广播到有序广播的转变。需要注意的是,sendOrderedBroadcast()方法的第二个参数要求传入权限相关的字符,这里传入null就可以了。如果现在你重新运行程序后点击Button按钮,依旧会分别弹出两次Toast,不过这个时候的BroadcastReceiver是有先后顺序的。
如果你想修改BroadcastReceiver接收广播的先后顺序,可以通过优先级来设定。我们可以在AndroidManifest.xml的< intent-filter >标签中添加android:priority="100"来设定该BroadcastReceiver的优先级为100。
<!-- 自定义MyBroadcastReceiver广播接收器 -->
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
设置优先级
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
这次我们再次运行程序并点击Button按钮发送广播,你会发现这次MyBroadcastReceiver的Toast弹窗永远会在AnotherBroadcastReceiver的Toast弹窗之前显示出现。这正是由于我们设置了MyBroadcastReceiver的优先级为100,以保证它一定会在AnotherBroadcastReceiver之前接收到广播。
在前面我们说到过,有序广播是可以被截断的。接下来我们尝试让MyBroadcastReceiver截断广播,这样AnotherBroadcastReceiver就无法再收到这条广播了。截断有序广播也很简单,我们只需要在MyBroadcastReceiver中调用 abortBroadcast()方法就可以截断这条广播。
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "广播已被MyBroadcastReceiver接收!", Toast.LENGTH_SHORT).show()
//截断广播
abortBroadcast()
}
}
这次我们点击Button按钮发送广播后,就只会弹出MyBroadcastReceiver中的Toast弹窗了。也就说明这条广播经过MyBroadcastReceiver之后确实终止传播了。