手写消息总线LiveDataBus

Android四大组件和线程间通讯方式有不少,好比Handler管道、广播、接口回调、rxBus、EventBus等,可是这些方式都存在一些瑕疵,具体的优缺点以下:
git

那么有没有一种通讯方式能够集以上全部框架的优势于一身,而且避免以上缺点呢?答案就是做者今天要分享的livedatabus,livedatabus是基于原生的livedata实现的通讯框架,它拥有如下的优势:
github


首先咱们来看一下LiveDataBus的总体架构,消息总线用来保存全部的消息通道,而后订阅者订阅其中任意的通道,发布者向通道发布消息
设计模式


LiveDataBus核心原理是什么?
bash

LiveDataBus原理其实就是发布-订阅模式+liveData,接下来做者会一一道来。首先说说发布-订阅模式,这个模式和观察者模式有些相似,甚至在有的设计模式书籍里也认为这2个模式是等同的。我我的以为仔细分析的话仍是有一些不一样的地方,最大的地方在于在观察者模式中观察者和被观察者是互相知道对方的,可是发布-订阅模式中订阅者并不知道发布者是谁。因此在须要对两者进行解耦时最好使用发布-订阅模式,发布者不须要知道订阅者的存在,两者只是共用一个信息通道。通常是在单线程中使用观察者模式,可是若是是在不一样线程中通讯就用发布-订阅模式会更适合。网络

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

观察者模式:
框架

发布-订阅者模式:异步

在客车里乘客至关于观察者,上车时乘务员经过买票知道了每位乘客到站信息,因此乘客只须要时刻观察乘务员的指示,当到站点时乘务员会给出当面指示,到站点的乘客能够根据乘务员的当面提示执行下车操做。可是在火车上乘务员不可能一个一我的去通知到站,只能经过发送广播的方式,并且乘客并不知道是哪一个乘务员发送的广播。
ide

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

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是一个官方支持的高效率、无内存泄漏、简单的优秀通讯框架。

相关文章
相关标签/搜索