Android 四大组件和线程间通信方式有很多,比如 Handler 管道、广播、接口回调、rxBus、EventBus 等,但是这些方式都存在一些瑕疵,具体的优缺点如下:

那么有没有一种通信方式可以集以上所有框架的优点于一身,并且避免以上缺点呢?答案就是作者今天要分享的 livedatabus,livedatabus 是基于原生的 livedata 实现的通信框架,它拥有以下的优点:


首先我们来看一下 LiveDataBus 的整体架构,消息总线用来保存所有的消息通道,然后订阅者订阅其中任意的通道,发布者向通道发布消息


LiveDataBus 核心原理是什么?

LiveDataBus 原理其实就是发布 - 订阅模式 +liveData,接下来作者会一一道来。首先说说发布 - 订阅模式,这个模式和观察者模式有些类似,甚至在有的设计模式书籍里也认为这 2 个模式是等同的。我个人觉得仔细分析的话还是有一些不同的地方,最大的地方在于在观察者模式中观察者和被观察者是互相知道对方的,但是发布 - 订阅模式中订阅者并不知道发布者是谁。所以在需要对二者进行解耦时最好使用发布 - 订阅模式,发布者不需要知道订阅者的存在,二者只是共用一个信息通道。一般是在单线程中使用观察者模式,但是如果是在不同线程中通信就用发布 - 订阅模式会更适合。

观察者模式和发布 - 订阅者模式对比

观察者模式:

发布 - 订阅者模式:

在客车里乘客相当于观察者,时刻观察乘务员的指示,当到站点时乘务员会给整出指示,到站点的乘客可以执行下车操作

接下来说说 LiveData,首先看一下 LiveData 的定义:

LiveData 是一个数据持有类,持有数据并且这个数据可以被观察被监听,和其他 Observable(被观察者) 不同的是,它是和 Lifecycle 绑定的,在生命周期内使用有效,减少内存泄漏和引用问题。

适合的使用场景这里举个例子,我们通过网络下载数据需要耗时一般放在子线程中,但是并不知道什么时候会下载完成,如果我们不使用 LiveData,那么就有可能出现等数据回来时主线程的界面已经被销毁的情况,这样就有可能出现问题了。这里如果使用 LiveData,就不需要管数据什么时候回来,回来后界面是否存在了,因为 LiveData 是自带生命周期监测的。

接下来我们来简单使用一下 LiveData

步骤一    获取 MutableLiveData 对象

NameViewModel mModel = ViewModelProviders.of(this).get(NameViewModel.class);复制代码

这个获取方式比较奇怪,首先 NameViewModel 是我们的一个自定义类,内容非常简单,就是获取到了一个系统的 MutableLiveData 对象而已,不过需要注意的是这个类一定要继承 ViewModel,不然是要报错的,获取不到 MutableLiveData,具体代码如下:

public class NameViewModel extends ViewModel {    
       private MutableLiveData<String> mCurrentName;    
       public MutableLiveData<String> getCurrentName() {        
              if (mCurrentName == null) {            
                       mCurrentName = new MutableLiveData<>();        
              }       
               return mCurrentName;    
       }
}复制代码

步骤二    新建观察者类

final Observer<String> nameObserver = new Observer<String>() {   
     @Override    
     public void onChanged(String s) {        
           nameText.setText(s);    
    }
};复制代码

步骤三    将观察者类传入 LiveData

mModel.getCurrentName().observe(this, nameObserver);复制代码

注意,这里的 mModel.getCuttentName 其实就是 MutableLiveData 对象,这个 this 就是当前 activity 的引用,也就是说将当前 activity 引用传入了 observe 方法,这个其实就是 LiveData 能监测到当前 Activity 生命周期的原因所在,具体怎么监测下面会详细讲到。

步骤四    发送消息给观察者

mModel.getCurrentName().postValue(anotherName);复制代码

注意,这里的 anotherName 是在 NameViewModel 中设置好的泛型,详见第一步中 MutableLiveData 的对象获取,指定了 anotherName 只能传递 String 过去。另外 postValue 方法是使用在异步线程中,setValue 使用在主线程中,都是发送消息。

LiveData 是如何做到监测页面的生命周期的?

这个就必须从源码着手了,我们首先看一下将 Activity 传进去的 MutableLiveData 中的 observe 方法

发现在这个方法中,将 Activity 对象和观察者对象传入了 LifecycleBoundObserver 中,所以我们点进去看一下 LifecycleBoundObserver 是一个什么样的类,然后它接收到了这 2 个对象以后都做了一些什么操作

我们看到 LifecycleBoundObserver 实现了 GenericLifecycleObserver,然后 GenericLifecycleObserver 又继承了 LifecycleObserver, 而这个类正是系统检测页面生命周期改变相关的类。根据 lifecycle 的用法,实现了 LifecycleObserver 并且将观察者传入就可以在生命周期改变时通知该观察者。另外在其中我们发现在 onStateChanged 中,如果当前页面状态是 destroy 的话,就移除我们的观察者,这样观察者就收不到回调了。

LiveData 是不是就足够解决业务中的问题了?

根据上面 LiveData 的基本使用,每更新一个控件就需要定义一个 NameViewModel,因为需要不同的 LiveData,原因是观察者的接口回调决定的,因为一个 LiveData 会执行一个 onChange 方法,但是一次只能带来一个参数,所以不能让所有的控件都获取到想要的值,所以我们必须想办法进行优化,那就是 LiveDataBus。

LiveDataBus 应该如何构建?

LiveDataBus 其实就是用 map 保存所有的 LiveData, 以唯一字符串作为 key,在使用的地方进行传入 key,获取到 map 中保存的 MutableLiveData


但是,我们接下来做一个尝试,在 A 页面发送消息给 B 页面,若此时 B 页面还没启动。过一段时间后启动 B 页面还会收到消息,这是不合理的,因为发送消息的时候 B 页面还没启动,所以那个时候发送的消息不应该被收到。当然这里如果想做得更好可以让使用者进行设置,让自定义的 LiveDataBus 支持粘性事件,这里可以参考一个第三方的LiveEventBus的实现。这次我们主要讲解一下如何通过 hook 技术取消这个粘性消息的接收,即在页面未打开时,就算后面打开了也不接收消息。

想要解决这个问题就要从源码入手了,我们首先从调用的源头 MutableLiveData 类中的 setValue 开始研究


我们看到,这个 setValue 其实就是调用了父类 LiveData 中的 setValue,所以我们找到看下


可以看到这里面调用了 dispatchingValue,所以我们点进去看看

这里面核心是 condiderNotify 方法,所以我们当然要进去看看了

最后一行是不是很眼熟?没错,这就是观察者的接口回调方法。大家要是不信可以反过来看也可以,首先到观察者的接口回调方法,然后 find useages 一样可以看到是这个方法。那么应该如何让这个消息第一次订阅 Livedata 的时候,这个 onChange 方法不执行呢?这个就必须用到修改系统代码执行流程的 hook 技术了。

从上面代码可以看出,上面有 3 个判断,只要其中有一个判断执行了那么都不会跑到最后的 onChange 方法,经过详细分析这里最好改的是第三个判断。在第三个判断中只要让 observer.mLastVersion >= mVersion 就不会执行 onChanged 了,那么应该如何让这两者符合要求呢?

首先看一下这个 mLastVersion 和 mVersion 是在哪里赋值的,先看 mLastVersion 吧,mLastVersion 赋值总共有 3 个地方,前两个是将 mLastVersion 赋值成和 mVersion 相等,这个不用考虑,因为这就是我们想要的结果,最后那一次是赋值成一个变量,而且是在初始化的时候赋值的,这个地方是在 private abstract 修饰的一个内部类中,没法进行修改。所以我们只能寄希望于 mVersion 身上了,我们看到 mVersion 的赋值处第一个是赋值为变量,这个是在 LiveData 的成员属性中赋值的,在类加载的时候就会创建,这里就算修改也会被 mLastVersion 复制过去,所以关键不在这里。我们把目标看向 mVersion++,没错就是这里打破了二者的平衡,让 mVersion+1,最后的结果就是 observer.mLastVersion<mVersion,导致那个判断没有进去,最后执行到了 onChanged 方法。那么这个 mVersion++ 是在哪里执行的呢?这个是在 setValue 方法中执行的。所以经过分析,我们利用好给为初始化的页面发送消息是先发送后注册这个特点。只需要在判断 observer.mLastVersion>=mVersion 之前将二者赋值为相等即可,换句话说,我们需要在 setValue 后的某个地方将这二者赋值为相同即可。


mLastVersion 是在 observer 对象中,而 observer 对象时 considerNotify 方法的参数传进来的,而 considerNotify 方法是在 dispatchingValue 方法中调用的,进入 dispatchingValue 中可以看到,实际上传下去的值是 mObservers 这个 map 中的值,也就是说我们只需要对当前页面对应的 Observer 进行修改即可


修改的方式就是反射,首先拿到 LiveData 中的 mObservers 这个 map,接下来获取到当前页面对应的 Observer,然后调用其中的 get 方法获取到 Entry,然后调用 set 方法将其设置成 mVersion 的值,实际代码如下


核心原理:当进入一个新页面时,会执行对 observers 的初始化,其中调用 hook 方法对 mLastVersion 进行修改,导致系统流程走不到 onChanged 方法。当再次发消息时,由于已经初始化过了,所以不会走到 hook 方法,就是正常流程,mLastVersion 值为 -1,mVersion 执行了 ++ 以后值变为了 0,这样就会走入 onChange 方法了,所以可以正常跑起来。

总结:本节我们分析了很多跨线程、页面通信的方法,总结了它们的优缺点,并且介绍了发布 - 订阅模式和观察者模式的区别。经过对比很多通信方式我们最终选择了 LiveDataBus,并且进行了模仿手写,解决了其中发现的问题。总而言之,LiveDataBus 是一个官方支持的高效率、无内存泄漏、简单的优秀通信框架。

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...