用广播 BroadcastReceiver 更新 UI 界面真的好吗?全方位解析广播

你们好,因为公众号有一个勘误,因此在掘金从新更正后发布本文。android

这是 面试系列 的第三期。本期咱们未来探讨一下 Android 四大组件的重要组成部分:广播 BroadcastReceiver。git

往期内容传递:
Android 面试:说说 Android 的四种启动模式
Android 面试:如何理解 Activity 的生命周期github

前言

BroadcastReceiver 做为 Android 四大组件之一,应用场景可谓很是之多。因此我相信任何一个有必定 Android 开发经验的工程师都不会在这个题上栽跟斗。但,某些细节,或许咱们能够注意一下。面试

实际上我在面试过程当中也遇到了这样的题。下面请容许我用「柳学兄」的思路带你们进入面试营。设计模式

BroadcastReceiver 内部基本原理是什么?

Android 的广播 BroadcastReceiver 是一个全局的监听器,主要用于监听 / 接收应用发出的广播消息,并做出响应。其采用了设计模式中的 观察者模式 ,可将广播基于 消息订阅者消息发布者消息中心(AMS:即 Activity Manager Service)解耦,经过 Binder 机制造成订阅关系。安全

图片来源于网络
图片来源于网络

说说 BroadcastReceiver 的两种注册方式

Android 广播的两种注册方式确定难不倒任何人,实际上我估计也只有对少许的 Android 开发面试者才会遇到这样的题,这里不会有什么特别的,熟悉的能够直接跳过bash

  • 静态注册
    静态注册广播的方式只须要在 AndroidManifest.xml 里经过 标签声明。下面附上一些属性说明。网络

    <receiver 
      android:enabled=["true" | "false"]
      //此 broadcastReceiver 可否接收其余 App 发出的广播
      //默认值是由 receiver 中有无 intent-filter 决定的:若是有 intent-filter,默认值为 true,不然为 false
      android:exported=["true" | "false"]
      android:icon="drawable resource"
      android:label="string resource"
      //继承 BroadcastReceiver 子类的类名
      android:name=".mBroadcastReceiver"
      //具备相应权限的广播发送者发送的广播才能被此 BroadcastReceiver 所接收;
      android:permission="string"
      // BroadcastReceiver 运行所处的进程
      // 默认为 App 的进程,能够指定独立的进程
      //注:Android 四大基本组件均可以经过此属性指定本身的独立进程
      android:process="string" >
    
      //用于指定此广播接收器将接收的广播类型
      //本示例中给出的是用于接收网络状态改变时发出的广播
       <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
      </intent-filter>
    </receiver>复制代码
  • 动态注册
    动态注册方式是经过调用 Context 下面的 registerReceiver() 进行注册,能够调用 unregisterReceiver() 进行注销。须要注意的是:动态广播最好在 Activity 的 onResume() 注册,并在 onPause() 进行注销。

为何建议动态广播尽可能在 onPause() 进行注销?

咱们能够先看看 Activity 的生命周期。异步

图片来源于网络
图片来源于网络

首先有注册就得有注销,不然必定会形成内存泄漏。注意上面途中红框圈住的部分。,阅读官方源码发现,当系统由于内存不足须要回收 Activity 占用的资源时,Activity 在执行完 onPause() 方法后就可能面临着被销毁的危险,有些生命周期方法,如:onStop()onDestroy() 根本就不会执行,而 onPause() 因为必定会调用的特殊性,天然是避免内存泄漏的好方法。工具

两种注册方式的区别也是能够用图一目了然。

图片来源于网络
图片来源于网络

说说 Android 的经常使用广播类型吧

基本在 Android 领域经常使用的方式就是直接调用 Context 提供的方法 sendBroadcast()sendOrderBroadcase() 发送无序广播和有序广播。

  • 无序广播
    无序广播是彻底异步的,经过 Context.sendBroadcast() 方法来发送,从效率上来看,还算是比较高的。正如它的名称同样,无序广播对全部的广播接收者而言,是无序的。也就是说,全部接收者没法肯定接收时序的顺序,这样也致使了,无序广播没法被中止。当它被发送出去以后,它将通知全部这条广播的接收者,直到没有与之匹配的广播接收者为止。

  • 有序广播
    有序广播经过 Context.sendOrderedBroadcast() 方法来发送。有序广播和无序广播最大的不一样,就是它能够容许接收者设定优先级,它会按照接收者设定的优先级依次传播。而高优先级的接收者,能够对广播的数据进行处理或者中止掉此条广播的继续传播。广播会先发送给优先级高 (android:priority) 的 Receiver,并且这个 Receiver 有权决定是继续发送到下一个 Receiver 或者是直接终止广播。

除了无序广播和有序广播,还有其余的类型吗?

可能仍是有很多的朋友知道 Sticky 广播方式。

  • 粘性广播 Sticky
    Sticky 广播和它的名字很像,它是一个具备粘性的广播。它被发出去以后,会一直滞留在系统中,直到有与之匹配的接收者,才会将其发出去。它采用 Context.sendStickyBroadcast() 方法进行发送广播。

    从官方文档上能够看到,若是想要发送一个 Sticky 广播,须要具备 BROADCAST_STICKY 权限,这个能够在 AndroidManifest.xml 中进行注册,而若是没有此权限,则会抛出 SecurityException 异常。

    对于系统而言,只会保留最后一条 Sticky 广播,而且会一直保留下去,也就是说,若是咱们发送的 Sticky 广播不被取消,当有一个接收者的时候就会收到它,再来一个仍是能收到。全部咱们须要在合适的实际,调用 removeStickyBoradcast() 方法,将其取消掉。

    从官方文档中也能够看到 StickyBroadcast 已经被标记为 @Deprecated ,出于一些安全的考虑,已经将其标记为废弃,再也不推荐使用。咱们做为开发者,对于一些被标记为 @Depracated 的方法,使用起来仍是须要谨慎的。

有时候基于数据安全考虑,咱们想发送广播只有本身(本进程)能接收到,怎么处理?

首先,Android 中的广播能够跨进程通讯,由于 exported 对于有 Intent-filter 的状况下默认为 true。因此咱们难以有这样的需求:

  • 对于某些敏感性的广播,咱们不但愿暴露给外部。
  • 其余 App 可能会发出和当前 App intent-filter 相匹配的广播,致使 App 不断进行广播接收和处理。

这真是一个坏消息,咱们必须让咱们的应用变得有效率并足够的安全

通常咱们能天然地想到在注册广播的时候把 exported 值设为 false 并给 App 的广播增长上权限,可问题是权限不够是一个字符串,面对当前如此强大的反编译技术,这终究是不安全的。

为了解决这样的问题,咱们不难想到能够经过往主线程的消息池(Message Queue)里发送消息,让其作到只有主线程的 Handler 能够分发处理它。或者在发送广播的时候直接经过 Intent.setPackage(packageName) 指定广播接收器的包名。

要不是咱们项目中有个 BroadcastUtil 工具类,我还以前真不知道 Support V4 包下还有这么一个 LocalBroadcastManager 本地广播类。

本地广播 在 Android Support v4 : 21 版本后加入了咱们的你们庭。它使用 LocalBroadcastManager (如下简称 LBM)类来管理。

LocalBroadcast 的使用很是的简单,只须要将 Broadcast 的对应 API,替换为 LBM 为咱们提供的 API 便可。

LBM 是一个单例对象,可使用 LocalBroadcastManager.getInstance(Context context) 方法获取到。在 Context 中定义的和 Broadcast 相关的方法,在 LBM 中都有对应的 API 。很是有意思的是,LBM 为了区分异步和同步,使用了 sendBroadcast()sendBroadcastSync() 方法来作为区分。

在 Android 中用广播来更新 UI 界面好吗?

废话扯了这么多,终于说到标题上的问题了。

直接回答:能够,为何不能够呢?在实际开发中咱们不是常常这么用么?

很好,能够确定你是一个真实的 Android 开发者了,不过在认证你的「合格」以前,想问问 BroadcastReceiver 的生命周期。

什么?BroadcastReceiver 的生命周期?糟糕,面试前只复习了 Activity 和 Fragment 的生命周期,杂还有人问 BroadcastReceiver 的生命周期。

因此,你支支吾吾了。

其实仍是有比较多的人了解 BroadcastReceiver 的生命周期的。BroadcastReceiver 有生命周期,但比较短,并且很短。当它的 onReceive() 方法执行完成后,它的生命周期也就随之结束了。这时候因为 BroadcastReceiver 已经不处于 active 状态,因此极有可能被系统干掉。也就是说若是你在 onReceive() 去开线程进行异步操做或者打开 Dialog 都有可能在没达到你要的结果时进程就被系统杀掉了。

因此,正确答案是?

更新 UI 界面这个定义太普遍了。实际开发中其实大多数状况都是能够采用 BroadcastReceiver 来更新 UI,因此也形成了不少人回答就想上面很确定和自信的回答能够。

实际上咱们知道 Receiver 也是运行在主线程的,不能作耗时操做。虽然超时时间相对于 Activity 的 5 秒更高,有足足的 10 秒。但不意味着咱们实际开发中全部的更新 UI 界面操做时间都在安全范围以内。

此外,对于频繁更新 UI,也不推荐这种方式。Android 广播的发送和接收都包含了必定的代价,它的传输都是经过 Binder 进程间通讯机制来实现的,那么系统确定会为了广播能顺利传递而作一些进程间通讯的准备。并且可能会因为其它因素致使广播发送和到达不许时(或者说接收会延迟)。

这种状况可能吗?

极可能,并且很容易发生。咱们要先了解 Android 的 ActivityManagerService 有一个专门的消息队列来接收发送出来的广播,sendBroadcast() 执行完后就当即返回,但这时发送来的广播只是被放入到队列,并不必定立刻被处理。当处理到当前广播时,又会把这个广播分发给注册的广播接收分发器ReceiverDispatcher,ReceiverDispatcher 最后又把广播交给接 Receiver 所在的线程的消息队列去处理(就是你熟悉的 UI 线程的 Message Queue)。

整个过程从发送 ActivityManagerService 到 ReceiverDispatcher 进行了两次 Binder 进程间通讯,最后还要交到 UI 的消息队列,若是基中有一个消息的处理阻塞了 UI,固然也会延迟你的 onReceive() 的执行。

BroadcastReceiver 和 EventBus 有啥不一样?

EventBus 做为 GitHub 上一个颇受欢迎的库,目前也是有着 16.3 k 的星星,足以见其强大。

因此在很多面试中固然会遇到这样的提问。这不,笔者在咕咚面试的时候就被面试官问到了这个题,又一个打脸,当时我像被电了一番,答的并不怎么样。

众所周知,广播是 Android 的四大组件之一。系统系统级的事件都是经过广播来通知的,好比说网络的变化、电量的变化、短信接收和发送状态等。因此,若是是和 Android 系统相关的通知,咱们还得选择本地广播。

可是!!!广播相对于其余实现方式,是很重量级的,它消耗的资源较多。它的优点体如今和 SDK 的紧密联系,onReceive() 方法自带了 Context 和 Intent 参数,因此在必定意义上实现了便捷性,但若是对 Context 和 Intent 应用不多或者说只作不多的交互的话,使用广播真的就是一种浪费!!!

那 EventBus 呢?

先说说其优势:

  • 调度灵活
    要说到优势,这必定是我最早想到的。由于它真的是太灵活了,在实际开发中感受它就是一个机灵鬼,想去哪就去哪,根本就不须要像广播同样关注 Context 的注入与传递。父类对于通知的监听和处理还能够直接继承给子类,能够设置优先级让 Subscriber 关注到优先级更高的通知,其粘滞事件(sticky events)可以保证通知不会因 Subscriber 的不在场而忽略。可继承、优先级、粘滞,是 EventBus 比之于广播、观察者等方式最大的优势,它们使得建立结构良好组织紧密的通知系统成为可能。

  • 使用简单
    进入到 EventBus 的官网,看一眼 README.md,简直不能再简单,简简单单三个步骤,再在 build.gradle 中添加一个依赖,轻轻松松搞定有木有?若是不想建立 EventBus 的实例,还能够直接调用静态方法 EventBus.getDefault() 获取。

  • 快速且轻量
    做为一个 GitHub 的明星项目,性能方面是能够放心的。

EventBus 这么棒,那咱们有组建通讯就用 EventBus 吧。

还真是人无完人,物无完物。EventBus 也有着它的致命弱点。EventBus 最大的缺点在于其逻辑性,直接看其代码,一不当心根本看不通有没有?另一个问题是,当程序较大后,观察者独有的接口膨胀缺点也会伴随着你的项目,你能想象不少 Event 后缀类的感受吗?

综上,EventBus 因为其针对统一进程,因此在某些复杂的状况下单纯依靠接口回调很差处理组件通讯的时候,直接去尝试 EventBus 吧。

说了这么多,在广播和 EventBus 这个十字路口犹豫不决的时候,还会纠结选择吗?

欢迎关注南尘的公众号:nanchen
若是你喜欢,你能够选择分享给你们。若是你有好的文章,欢迎投稿,赞扬所有归你全部。

长按上方二维码关注
                  作不完的开源,写不完的矫情
                  一块儿来看 nanchen 的成长笔记复制代码
相关文章
相关标签/搜索