10s执行不完就ANR异常。
一、概念
Broadcast运行在主线程,使用了观察者模式,基于消息的发布/订阅事件模型,将发送者和接收者解耦使得方便集成更易扩展。
广播接收者通过 Binder 机制注册到 AMS中,AMS根据广播发送者要求在已注册列表中寻找合适的接收者(依据IntentFilter/Permission)。发送和接收的执行是异步的,广播的发送不会管有没有接收者能收到,接收者注册后才能监听到。
全局广播 | APP ↔ 系统 | 像电量低、锁屏解锁、SD卡挂载、电量不足、网络改变.....这些事件到来时系统就会发送广播,监听这些广播就可以在对应的情况下做一些事情,例如锁屏后是否暂停音乐播放。 |
APP ↔ APP | 不同APP之间通信。 | |
APP内部 | 各组件之间通信。 | |
本地广播 | APP内部 | 全局广播存在安全问题,恶意程序能发送你能接收的广播(非法控制)接收你发出的广播(数据泄露)。 |
<Android SDK>/platforms/<
任意
android api
版本
>/data/broadcast_actions.txt
查看完整的系统广播列表。
| |
短信提醒 | Android.Provider.Telephony.SMS_RECEIVED |
电量过低 | ACTION_BATIERY_LOW |
电量发生改变 | ACTION_BATTERY_CHANGED |
连接电源 | ACTION_POWER_CO |
二、接收广播 BroadcastReceiver
我们可以对自己感兴趣的广播自由地注册BroadcastReceiver,当有相应的广播发出
时,相应的BroadcastReceiver就能够收到该广播,并可以在内部进行逻辑处理。
动态注册 | 非常驻,受组件生命周期影响。 |
静态注册 | 常驻,即便APP关闭依旧会响应广播。 |
2.1 动态注册(在代码中)
动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。必须在组件启动之后才能接收广播,受组件的生命周期影响。
在 onResume() 中开始监听,在 onPause() 中取消监听,否则容易造成内存泄漏。
重复注册和取消抛异常?
Activity {
lateinit var myReceiver : MyReceiver
onResume() {
//想要监听什么广播,就添加相应的action进行过滤。
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.TIME_TICK")
//进行注册。
myReceiver = MyReceiver()
registerReceiver(myReceiver, intentFilter)
}
onPause() {
super.onDestroy()
unregisterReceiver(myReceiver ) //一定要取消注册。
}
//新建一个类继承BroadcastReceiver,并重写父类的onReceive()方法就行。
//一般是在专门的地方使用,不考虑复用的话就使用内部类的方式创建。
inner class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//TODO...
isOrderedBroadcast() //判断是有序广播还是标准广播
}
}
}
2.2 静态注册(在Manifest中)
静态注册需要在Manifest文件中配置才能使用。由于大量恶意APP利用这个机制在程序未启动的情况下监听系统广播,从而可以频繁地从后台被唤醒,在 Android 8.0 后所有隐式广播(intent.setPackage()没有具体指定发送给哪个APP的广播)都不允许使用静态注册了。查看隐式广播例外情况
//不用手写:在Android Studio中,右键包→New→Other→Broadcast Receiver。
//勾选Exported表示是否允许接收本程序以外的广播(会设置android:exported="true")
//勾选Enabled表示是否启用(会设置android:enabled="true")。
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//TODO...
}
}
<receiver
android:name=".MyReceiver" //名称
android:enabled="true"
android:exported="true" //是否能监听其它APP发送的广播,未指定的默认值根据是否有 intent-filter 决定,有为true
android:permission="string" //具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收
android:process="string" //默认为app的进程,可以指定独立的运行进程
>
//想要监听什么广播,就添加相应的action进行过滤。
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
2.2.1 配置权限
在发送端自定义权限,那么接收端必须声明权限才能接收到。
val intent = Intent().apply {
putExtra("info", "信息")
action = "MyAPP.info"
}
sendBroadcast(intent,"MyAPP.permission")
//sendOrderedBroadcast(intent,"MyAPP.permission")
//自身Manifest配置
<permission android:name="MyApp.permission" android:protectionLevel="normal"/>
<uses-permission android:name="MyApp.permission"/>
//其他应用Manifest配置
<uses-permission android:name="MyApp.permission"/>
//静态注册配置
<receiver android:name=".MyReceiver"
android:permission="MyApp.permission">
</receiver>
三、发送广播
标准广播 Normal Broadcasts | 异步的,可被所有接收者同时接受。接收者无法将处理结果交给下一个,无法终止广播。 |
有序广播 Ordered Broadcasts | 同步的,同一时刻只有一个接收者接受,处理后可以继续传递,也可以终止广播,可配置接收者的优先级,可以指定最终的接收者。 |
本地广播 Local Broadcast | |
系统广播 System Broadcast | Android中内置了很多系统广播,只要涉及到手机的基本操作(开机、网络变化、电量低、插入耳机、锁屏、重启设备、成功安装APK...)都会发送广播。 |
粘性广播 Sticky Broadcast | 于 Android 5.0(API21)中失效。 |
3.1 发送标准广播
是一种完全异步执行的广播,在广播发出之后,所有的BroadcastReceiver几乎会在同一时刻收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
//把要发送的广播的值传入
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
//传入当前应用程序的包名,指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播
//之前提到隐式广播在8.0后无法被静态注册的BroadReceiver接收
intent.setPackage(packageName)
//发送标准广播
sendBroadcast(intent)
3.2 发送有序广播
是一种同步执行的广播,在广播发出之后,同一时刻只会有一个BroadcastReceiver能够收到这条广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver是有先后顺序的,优先级高的BroadcastReceiver就可以先收到广播消息,并且前面的BroadcastReceiver还可以截断正在传递的广播,这样后面BroadcastReceiver就无法收到广播消息了。
android:property | Manifest中设置优先级。 |
abortBroadcast( ) | 丢弃广播,拦截后不会继续向后传递。 |
setResultData( ) | 设置数据传递给下个接收者。 |
getResultData( ) | 获取上个接收者设置的数据。 |
sendOrderedBroadcast( ) | public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission) 发送有序广播 |
public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) 发送有序广播,可以指定 resultReceiver 最终接收者。如果优先级高的接收者没有拦截广播,最终接受者的 onReceive() 会执行两次(正常和最终),如果拦截了会执行一次(最终)。 |
//把要发送的广播的值传入
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
//传入当前应用程序的包名,指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播
//之前提到隐式广播在8.0后无法被静态注册的BroadReceiver接收
intent.setPackage(packageName)
//发送有序广播
sendOrderedBroadcast(intent, null) //参数二权限相关
//manifest
<application>
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
//想要监听什么广播,就添加相应的action进行过滤。
<intent-filter android:priority="100"> //设置接收的优先级
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
abortBroadcast() //截断广播,后面优先级低的就会收不到了
}
}
3.3 发送本地广播
本地广播用于在同一个APP内不同组件间发送广播,无法通过静态注册的方式来接收,底层通过Handler实现相较于全局广播的Binder更高效,也因此本地广播只能在自身APP中传播,解决了全局广播存在的安全问题,因为恶意程序能发送你能接收的广播(非法控制)或接收你发出的广播(数据泄露)。
3.3.1 方式一:将全局广播设为局部广播
- 将 <exported> 设置为 false,不接收非本 APP 发出的广播。
- 设置 <permission> 用于发送和接收时的权限验证。
- 通过 intent.setPackage("") 发送时指定接收者所在包名,此广播将只会发送到对应包名APP中与之匹配的接收者。
3.3.2 方式二:使用封装好的LocalBroadcastManager类
只是发送和接收时将 contex 变成 LocalBroadcastManager 的单一实例。该方式只动态注册。
//注册应用内广播接收器
val broadcastReceiver = mBroadcastReceiver()
val intentFilter = IntentFilter()
val localBroadcastManager = LocalBroadcastManager.getInstance(this)
//设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//调用LocalBroadcastManager单一实例的registerReceiver()进行动态注册
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter)
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//发送应用内广播
val intent = Intent();
intent.setAction(BROADCAST_ACTION)
localBroadcastManager.sendBroadcast(intent)
四、实现强制下线功能
只需要在界面上弹出一个对话框,让用户无法进行任何其他操作,必须点击对话框中的“确定”按钮,然后回到登录界面即可。
4.1 关闭所有Activity
当用户被通知需要强制下线时,可能正处于任何一个界面,先关闭所有的Activity,然后回到登录界面。
object ActivityCollector { //使用单例类因为全局只需要一个Activity集合
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()
}
}
open class BaseActivity : AppCompatActivity() {
onCreate() { ActivityCollector.addActivity(this) }
onDestroy() { ActivityCollector.removeActivity(this) }
}
4.2 广播接收者处理事件
BaseActivity只需要提供通知功能(发送广播),具体处理逻辑应该写在接收这条通知的广播接收者中,这样就不用依附于任何界面了。由于需要弹出一个对话框只能动态注册,在BaseActivity中注册子Activity都可以接收。
open class BaseActivity : AppCompatActivity() {
lateinit var receiver: ForceOfflineReceiver
//栈顶的Activity才需要接收广播
onResume() {
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE")
receiver = ForceOfflineReceiver()
registerReceiver(receiver, intentFilter)
}
//Activity失去栈顶位置就注销监听
onPause() { unregisterReceiver(receiver) }
//提供一个发送广播的功能
fun offline() {
val intent = Intent("com.example.broadcastbestpractice.FORCE_OFFLINE")
sendBroadcast(intent)
}
//
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") { _, _ ->
ActivityCollector.finishAll() // 销毁所有Activity
val i = Intent(context, LoginActivity::class.java)
context.startActivity(i) // 重新启动LoginActivity
}
show()
}
}
}
}
4.3 LoginActivity设为启动Activity
继承BaseActivity,对帐号密码进行判断,正确就跳转到MainActivity,错误就提示。在 Manifest 中将默认启动界面改为LoginActivity,
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>