看了挺多文章的,大部分都是直接给最后结果的,这篇不是,而是一步一步的敲出最后的结果,有步骤的内容更有助于理解,可以知道为什么这么写~觉得很好就分享了,下文是转载自:RxBus的使用及解析
前言
相信项目中使用了Rxjava的你,一定会选择使用RxBus而不是EventBus作为数据总线工具。那么你真的了解RxBus吗?下面就跟着我一起来探个究竟吧。
注:本文基于Rxjava2.0,还在使用Rxjava1.0的童鞋赶紧升级一波吧
解析
我们都知道,Rxjava基于观察者模式,上游发送数据,下游通过回调接收数据。我们试想一下,假如我们在A页面发送数据,并把产生的Observable存放在全局变量中,在B页面拿到该Observable并订阅,是不是就可以获取到A页面发送的数据了?
是的骚年,这样想没错,至于行不行实验一把就知道了。首先我们写一个保存Observable全局变量的类。
public class RxTest {
public volatile Observable<String> instance;
}
其中volatile
关键字修饰的变量可以让线程看到的始终是最新值,线程1中对变量的最新修改,对线程2是可见的。
然后我们在Activity A页面,点击按钮发送数据,并保存在全局变量中,并打开Activity B页面。
Observable<String> observable = Observable.just("哈哈哈");
RxTest.instance = observable;
startActivity(new Intent(getContext(), TestActivity.class));
其中Activity B中的初始化中拿到该Observable并注册。
if (RxTest.instance != null) {
RxTest.instance.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.i(TAG, "onSubscribe");
}
@Override
public void onNext(String s) {
Log.i(TAG, "onNext:" + s);
}
@Override
public void onError(Throwable e) {
Log.i(TAG, "onError");
}
@Override
public void onComplete() {
Log.i(TAG, "onComplete");
}
});
}
OK,我们运行一下,点击发送按钮后,看下打印:
onSubscribe
onNext:哈哈哈
onComplete
可以啊!不错不错~就这么干了
稍等!少年还是太年轻啊,下面我们在另一个场景中试试。
这次我们在主页面的两个fragment中测试,在fragment A中发送数据,在fragment B中注册接收,代码不变。测试的结果是:啥也没有!在fragment B中根本就没有注册,更没有后续。
为啥?
这里存在一个时间的问题。在第一个例子中,我们先是生成了Observable,把它保存在全局中,然后才打开了Activity B页面,此时B页面从全局中拿到的是刚刚生成的Observable,注册拿到数据,没问题。但是第二个例子中,两个fragment几乎是同时加载的,fragment B中初始化的时候,全局中并没有保存任何Observable。
哎?这怎么办??
我们换个思路想,假如我们在A页面发送数据的,和B页面注册接收数据的,是同一个对象,问题是不是就解决了?
你别说,还真有这个东西,那就是Subject。我们看下它的继承关系:
public abstract class Subject<T> extends Observable<T> implements Observer<T>
emmmm…有点雌雄同体的赶脚……
我们看到,Subject是个抽象类,它有四个实现。分别是:
- PublishSubject:从哪里订阅就从哪里开始发送数据。
- AsyncSubject:无论输入多少参数,永远只输出最后一个参数。
- BehaviorSubject:发送离订阅最近的上一个值,没有上一个值的时候会发送默认值。
- ReplaySubject:无论何时订阅,都会将所有历史订阅内容全部发出。
按照我们数据总线的需求,我们应该选择第一个PublishSubject。我们来把上面的例子改造一番:
public class RxTest {
private static volatile RxTest mInstance;
private volatile Subject<String> mSubject;
private RxTest() {
mSubject = PublishSubject.create();
}
public static RxTest getInstance() {
if (mInstance == null) {
mInstance = new RxTest();
}
return mInstance;
}
/**
* 发送消息
*
* @param s
*/
public void post(String s) {
mSubject.onNext(s);
}
public Observable<String> getObservable() {
return mSubject;
}
}
我们把Observable换成Subject,并且做成单例模式,保证使用同一个,定义了发送数据和获取该Subject的方法。(因为Subject实现了Observer接口,所以有OnNext()方法。因为Subject继承了Observable,获取的时候可以转成Observable)。
现在,我们在fragment A页面发送数据:
RxTest.getInstance().post("嘿嘿嘿");
分别在fragment B和Activity B页面中注册(注意:此时Activity B尚未创建)。
RxTest.getInstance().getObservable()
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.i(TAG,"onSubscribe");
}
@Override
public void onNext(String s) {
Log.i(TAG,"onNext:"+s);
}
@Override
public void onError(Throwable e) {
Log.i(TAG,"onError");
}
@Override
public void onComplete() {
Log.i(TAG,"onComplete");
}
});
点击发送数据后,可以在fragment B中立刻获得发送值:
TreeFragment测试: onNext:嘿嘿嘿
而Activity B中是没有任何打印。此时打开Activity B页面,猜猜打印什么?
TestActivity测试: onSubscribe
只是执行了订阅,并没有在onNext()中获取到发送值,为什么呢?还记得我们使用的PublishSubject的特点吗?特点是从哪里订阅就从哪里开始发送数据。我们在打开Activity B之前,发送了一条数据,然后打开后才开始订阅,这时候是不会拿到上条数据的,会获取到后续发送的数据。而如果我们是使用了ReplaySubject,就可以拿到之前所有的数据。
现在我们把这个工具优化一下,我们总不能每次发消息都只发string字符串吧…
public class RxTest {
private static volatile RxTest mInstance;
private final Subject<Object> mSubject;
private RxTest() {
mSubject = PublishSubject.create().toSerialized();
}
public static RxTest getInstance() {
if (mInstance == null) {
synchronized (RxTest.class){
if (mInstance == null){
mInstance = new RxTest();
}
}
}
return mInstance;
}
/**
* 发送消息
*
* @param event
*/
public void post(Object event) {
mSubject.onNext(event);
}
public <T> Observable<T> getObservable(final Class<T> eventType) {
//转换为泛型为T的Observable
return mSubject.ofType(eventType);
}
/**
* 是否有观察者
*
* @return
*/
public boolean hasObservers() {
return mSubject.hasObservers();
}
}
那有小伙伴说了,我想实现像EventBus那样的粘性信息怎么弄?打开新页面还会获取到上一条发送的粘性信息。这个是可以实现的,我们可以在RxTest中维护一个hashMap,在发送粘性消息的时候,把消息对象保存在hashMap中,键为消息对象类型,值为消息对象。然后在注册粘性信息时,从hashMap中获取,处理完数据后将之移除。
到这里功能已经完成了,但是有一个隐患,假如由于某种错误导致Subject发送了onError或者onComplete,那么在所有注册了RxBus处,在接收到onError或onComplete后,不会再接收Subject后续发送的任何信息。如此该怎么办?
别急,技术改变世界,技术无所不能,咳咳……这时候需要隆重登场一位大神,他就是JakeWharton,著名的Rxbinding就是他的杰作。我们解决当前这个问题就需要用到他的RxRelay库,它和Subject的区别就是不必担心事件在onComplete或者onError后终止事件订阅关系。
添加一个依赖
implementation 'com.jakewharton.rxrelay2:rxrelay:2.0.0'
使用RxRelay优化我们的RxBus:
public class RxTest {
private static volatile RxTest mInstance;
private final Relay<Object> mBus;
private RxTest() {
this.mBus = PublishRelay.create().toSerialized();
}
public static RxTest getInstance() {
if (mInstance == null) {
synchronized (RxTest.class){
if (mInstance == null){
mInstance = new RxTest();
}
}
}
return mInstance;
}
/**
* 发送消息
*
* @param event
*/
public void post(Object event) {
mBus.accept(event);
}
public <T> Observable<T> getObservable(final Class<T> eventType) {
//转换为泛型为T的Observable
return mBus.ofType(eventType);
}
/**
* 是否有观察者
*
* @return
*/
public boolean hasObservers() {
return mBus.hasObservers();
}
}
OK,到这里就告一段落了,希望可以帮到大家,如有错误之处还请不吝赐教,不胜感激!