Android - Broadcast

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:propertyManifest中设置优先级。
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>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值