作者乘风(企业代号名),目前负责贝壳装修项目Android研发工作。
1 背景
在Android系统中,我们开发的时候不可避免的会用到消息传递,页面和组件之间都在进行消息传递,消息传递既可以用于Android四大组件之间的通信,也可用于主线程和子线程之间的通信。从一开始Android书本中学习的Handler、BroadcastReceiver、接口回调等方式,到我们现在广为使用到的greenrobot家的EventBus,Square家的Otto,还有依托响应式编程代表RxJava实现的RxBus,最近在浏览美团技术博客时发现@liaohailiang基于Android Architecture Components实现了一个名为LiveEventBus的新的事件总线,依靠LiveData这个类可以实现一个无须手动解除注册,无内存泄漏问题的事件总线,下面一起来了解一下。
2 从其他的时间总线说起
EventBus是Android和Java的发布/订阅事件总线。在它出现之前,我们往往使用Handler、Intent、BroadcastReceiver等方式进行数据传递,但这些都不能满足我们高效的开发。自从EventBus出现后,简化了组件之间都通信,将事件发送者和接收者隔离,在Activity和Fragment还有后台线程中表现良好,避免了复杂而且容易出错的依赖关系和生命周期问题,立马受到开发者的欢迎,它的思想就是消息的发布和订阅,这种思想在很多其他框架中都有体现。
从图中可以看出订阅发布模式是一种一对多的关系,同一个事件可以被多个订阅者接收,当发布者的状态发生变化时,订阅者都能收到通知进行数据更新。
RxBus 名字看起来像一个库,但它并不是一个库,而是一种模式,它的思想是使用RxJava来实现了EventBus,而让你不再需要使用Otto或者GreenRobot的EventBus。RxBus就是基于RxJava封装实现的类。随着RxJava在更多Android项目中的使用, 我们完全可以使用RxBus代替EventBus,减少库的引入,增加系统的稳定性。
EventBus虽然使用方便,但是在事件的生命周期的处理上需要我们利用订阅者的生命周期去注册和取消注册,这个部分还是略有麻烦之处,如果忘记了在生命周期结束回调中取消订阅,就会导致内存泄漏的问题,而我们可以结合使用RxLifecycle来配置,简化这一步骤。
在RxJava中有个Subject类,它继承Observable类,同时实现了Observer接口,因此Subject既可以作为被观察者发送事件,也可以作为观察者接收事件,而RxJava内部的响应式的支持实现了事件总线的功能。可以使用PublishSubject.create().toSerialized();生成一个Subject对象。
剩下的动作和EventBus一样了,在需要发消息的地方发送事件,那么订阅了该Subject对象的订阅者就会收到事件进行处理。
最后当页面finish的时候,如果没有取消订阅,就会和EventBus一样,导致Activity或Fragment无法回收引发内存泄漏,EventBus要求我们在页面结束时取消注册,所以RxBus也需要如此。在RxJava中,订阅操作会返回一个Subscription对象,以便在合适的时机取消订阅,防止内存泄漏,如果一个类产生多个Subscription对象,我们可以用一个CompositeSubscription存储起来,以进行批量的取消订阅。
网上RxBus有很多实现,例如AndroidKnife/RxBus,大家可以点击查看。
otto是square推出的一款应用在android上的轻量级事件总线框架,目的是为了解决消息通信的问题,通过反射帮助你在不持有对方引用的情况下通知到对方,缓解了移动开发中会遇到的耦合问题。
那么它有什么不足呢?
1)在注册的时候,因为otto要用反射遍历Object的所有方法,所以时间会拉长,对性能会有一定的影响,如果你的项目对性能要求并不那么高,那完全可以使用otto来减少代码量。
2)从源码里看,在基类中注册事件是一件比较麻烦的事情。
3)订阅事件的参数问题,otto只允许接收一个参数,不然会抛出RuntimeException。
4)项目通信场景较多而且复杂的时候,otto框架的拓展性就显得不够友好了。
目前,Square推荐使用RxJava实现RxBus方式来代替Otto,也就是说otto被square公司抛弃了。
3 LiveData基础
3.1 什么是LiveData?
LiveData是Android Architecture Components提出的框架,LiveData是一个可以在给定生命周期内被观察的数据持有者类,它可以感知并遵循Activity、Fragment或Service等组件的生命周期。正是由于LiveData对组件生命周期可感知特点,因此可以做到仅在组件处于生命周期的激活状态时才更新UI数据。如果观察者对应的生命周期变成了destroyed状态时,它会被自动的移除,这对Activity和Fragment来说是非常有用的,它们可以很安全的观察LiveData而不用担心泄漏问题,它们被销毁后将立即取消订阅。
3.2.1 observe模式
拥有生命周期,在started和resumed时触发回调,更新数据,无需手动解除注册,无内存泄漏问题。
3.2.2 observeForever模式
一直监听,没有生命周期,跟EventBus和RxBus一样,需要手动解除注册,否则有内存泄漏问题。
3.3.1 保证数据和UI的统一
因为LiveData采用了观察者模式,LiveData是被观察者,当数据改变时通知观察者(UI)及时更新。当组件从后台到前台来时,LiveData也能将最新数据通知组件进行更新。
3.3.2 避免内存泄漏
因为LiveData能够监听生命周期的变化,当生命周期变化为DESTROYED时,就会清除观察者对象,不仅可以编写更少的代码,而且可以避免其他事件总线忘记调用反注册带来的内存泄漏风险。
3.3.3 页面不可见时不会崩溃
因为只有当生命周期是STARTED或RESUMED时,LiveData才会通知数据变化,所以不用担心页面不可见时收到通知刷新数据导致的崩溃。
3.3.4 不需要额外处理响应生命周期变化
这一点还是因为LiveData能感知组件的生命周期,所以不需要我们告诉它生命周期状态。
3.3.5 解决configuration changed问题
当组件被recreate时,数据还是存在LiveData中,所以在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
3.4.1 减少apk包大小
因为只依赖Android官方的Android Architecture Components组件的LiveData,没有其他依赖,实现只有一个类。而EventBus jar包大小为57kb,RxBus依赖RxJava和RxAndroid,其中RxJava2包大小2.2MB,RxJava1包大小1.1MB,RxAndroid包大小9kb,otto就不说了,因为Square推荐使用RxJava实现RxBus方式来代替Otto[捂脸]。
3.4.2 依赖方支持更好
LiveEventBus只依赖Android官方Android Architecture Components组件的LiveData,相比RxBus依赖的RxJava和RxAndroid,依赖方支持更好。
4 LiveData代码实现
MutableLiveData是LiveData的子类,其中只重写了两个方法,代码如下:
先看看LiveData的声明:
它是一个抽象类,所以才有了MutableLiveData这个实现类。
接下来我们先从setValue入手看看LiveData的源码。
4.2.1 setValue(T value)
其中assertMainThread方法实际上是判断是否是主线程,不是则会抛出异常:
mVersion初始值为-1,这里就会变成0;
主要看看dispatchingValue(null);方法:
参数initiator为null,会走到红框中,接着看:
mLastVersion是在ObserverWrapper这个类中定义的,初始值为-1,在setValue的时候mVersion已经变成了0,所以红框中的逻辑不会进入,接着往下走,observer就会通过onChanged回调方法将我们设置的值通知给订阅者。
4.2.2 postValue(T value)
从注释知道该方法是供后台线程使用的,并且多次赋值的话以最后一次赋值为准,来看看红框里的代码:
代码里最终还是调用了setValue方法,回到了setValue的逻辑,只是帮我们处理了后台线程到主线程的切换工作。
赋值部分看完了,现在该看看注册订阅的代码了。
4.2.3 observe
LifecycleBoundObserver实现了GenericLifecycleObserver,在生命周期发生改变时会回调onStateChanged方法,如果Activity/Fragment对生命周期时destroyed的时候,就会触发removeObserver(mObserver)操作,否则就会执行activeStateChanged(shouldBeActive())方法。
activeStageChanged(shouldBeActive())是父类ObserverWrapper中定义的方法:
当生命周期处于STARTED或RESUMED时,就会执行红框中的逻辑,其实咱们上面已经看过了dispatchingValue方法,再来看一次:
到这里就出现了一个问题: 如果之前有调用过setValue方法的话,这里mVersion++每次会自增,所以肯定是>-1的,而observer中定义的mLastVersion初始值是-1,所以红框中的逻辑不会执行,代码往下执行走到observer.mObserver.onChanged((T) mData); 这样就会导致LiveData每注册一个新的订阅者,这个订阅者立刻会收到一个回调得到之前发布的消息,即使这个设置的动作发生在订阅之前。
由于是因为ObserverWrapper的mLastVersion和LiveData的mVersion不同步的问题导致的,所以可以通过继承MutableLiveData然后重写observe方法,LiveData提供getVersion()方法返回mVersion,我们只需把ObserverWrapper的mLastVersion设成mVersion就好了。
@liaohailiang在github上开源的LiveEventBus就是这么干的:
这样就能保证新注册的订阅者在初始
if (observer.mLastVersion >= mVersion) { return; }
执行这个方法时就return了,只有当它setValue或postValue赋值后,才会执行通知逻辑。
4.2.4 public void observeForever(@NonNull Observer
接着看看注册永久观察者方法里做了什么操作:
首先用了一个AlwaysActiveObserver类:
这个类的shouldBeActive方法return true;这个状态表明不受生命周期的影响了。
另外wrapper.activeStateChanged(true);也是传入了一个true,所以在
considerNotify(ObserverWrapper observer)中,订阅者永远会接收到最新的发布消息。
4.2.5 removeObserver(@NonNull final Observer
在LifecycleBoundObserver中,由于实现了GenericLifecycleObserver,所以当订阅者通过observe方式订阅的时候,当生命周期发生变化时会回调该方法:
当监听到页面当生命周期为DESTROYED会通知移除对应的观察者:
从mObservers列表中移除生命周期所有者对应的观察者,解除绑定观察者,通知状态变化。
Activity或Fragment则会移除观察者对象。
需要注意的是这对observeForever方式注册的观察者并不生效。
5 总结
Android官方提供了Android Architecture Components组件的LiveData,具有很多其他事件总线不具备的优点,能方便的感知到页面的生命周期变化从而进行数据通知,我们也能基于这个类自己实现出一套性能优异的事件总线,这里推荐一个@liaohailiang在GitHub开源的项目代码:LiveEventBus,工程包含了其他的示例代码的使用本文没有做演示,只是对LiveData的核心原理做一个剖析,感兴趣的同学可以自行查阅代码。
作 者: 乘风 (企业代号名)
出品人:漠北鹰、刘伯温(企业代号名)
---------- END ----------
设计模式之禅——设计原则
iOS缓存设计之YYCache