前言

相信项目中使用了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>() {@Overridepublic void onSubscribe(Disposable d) {Log.i(TAG, "onSubscribe");}@Overridepublic void onNext(String s) {Log.i(TAG, "onNext:" + s);}@Overridepublic void onError(Throwable e) {Log.i(TAG, "onError");}@Overridepublic 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>() {@Overridepublic void onSubscribe(Disposable d) {Log.i(TAG,"onSubscribe");}@Overridepublic void onNext(String s) {Log.i(TAG,"onNext:"+s);}@Overridepublic void onError(Throwable e) {Log.i(TAG,"onError");}@Overridepublic 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的Observablereturn 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的Observablereturn mBus.ofType(eventType);}/*** 是否有观察者** @return*/public boolean hasObservers() {return mBus.hasObservers();}
}

OK,到这里就告一段落了,希望可以帮到大家,如有错误之处还请不吝赐教,不胜感激!

RxBus的使用及解析相关推荐

  1. Android 消息总线汇总(一)

    消息总线的演进之路: 广播,Handler,–>EventBus–>RxBus–>LiveDataBus Handler 面试:Handler内存泄露的原因是什么? https:// ...

  2. android对象内存泄漏,Android内存泄漏和检查——结合项目实例解析

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 前言 在我们版本迭代的过程中,内存泄漏是我们时刻关注,但又经常忽略的烦人问题.几乎每个大版本迭代都会出现新的内存泄漏点, ...

  3. RxJava 从入门到全解析

    前言 使用了RxJava有一段时间了,深深感受到了其"牛逼"之处.下面,就从RxJava的基础开始,一步一步与大家分享一下这个强大的异步库的用法! RxJava 概念初步 RxJa ...

  4. golang通过RSA算法生成token,go从配置文件中注入密钥文件,go从文件中读取密钥文件,go RSA算法下token生成与解析;go java token共用

    RSA算法 token生成与解析 本文演示两种方式,一种是把密钥文件放在配置文件中,一种是把密钥文件本身放入项目或者容器中. 下面两种的区别在于私钥公钥的初始化, init方法,需要哪种取哪种. 通过 ...

  5. List元素互换,List元素转换下标,Java Collections.swap()方法实例解析

    Java Collections.swap()方法解析 jdk源码: public static void swap(List<?> list, int i, int j) {// ins ...

  6. 条形码?二维码?生成、解析都在这里!

    二维码生成与解析 一.生成二维码 二.解析二维码 三.生成一维码 四.全部的代码 五.pom依赖 直接上代码: 一.生成二维码 public class demo {private static fi ...

  7. Go 学习笔记(82)— Go 第三方库之 viper(解析配置文件、热更新配置文件)

    1. viper 特点 viper 是一个完整的 Go应用程序的配置解决方案,它被设计为在应用程序中工作,并能处理所有类型的配置需求和格式.支持特性功能如下: 设置默认值 读取 JSON.TOML.Y ...

  8. Go 学习笔记(77)— Go 第三方库之 cronexpr(解析 crontab 表达式,定时任务)

    cronexpr 支持的比 Linux 自身的 crontab 更详细,可以精确到秒级别. ​ 1. 实现方式 cronexpr 表达式从前到后的顺序如下所示: 字段类型 是否为必须字段 允许的值 允 ...

  9. mybatis配置文件解析

    mybatis配置文件解析 mybatis核心配置文件`mybatis-config.xml文件. mybatis的配置文件包含了会深深影响mybatis行为的设置和属性信息. 能配置的内容: con ...

  10. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

最新文章

  1. Socket 学习(三).1 tcp 通讯
  2. python删除列表元素_追求简单C++之删除STL列表的元素
  3. 跨浏览器开发:CSS代码的金科玉律
  4. 生产上完成TopN统计流程
  5. 细数Android开发者的艰辛历程,全网最新
  6. POJ 3690 找星座(2D匹配)(未解答)
  7. lucene-5.3.1配置(win7x64)
  8. dj电商-模型类设计-商品模块数据表
  9. A child container failed during start 解决方案
  10. 2016: 神殿(求二进制1的个数最多的那个数)
  11. 深入理解IIS工作原理
  12. 自然语言处理(NLP)与自然语言理解(NLU)的区别
  13. 《初识Scratch》教学设计
  14. 【非标自动化】2017年的最NB的非标自动化内容都在这了
  15. Word2016如何插入公式?Word2016插入公式方法
  16. 网络安全工程师年薪百万?到底是干什么的?
  17. linux看磁带内容命令,Linux下磁带管理命令
  18. sourceTree使用教程
  19. 人工智能技术与专利技术变革
  20. 上下文切换是在做什么事情?

热门文章

  1. 自然语言处理课程作业 中文文本情感分类
  2. 从全息投影到全息平台,必须克服7个障碍
  3. 安川ga700变频器故障码集_安川变频器故障代码
  4. Windows下功能强大注册表整理、修复软件RegClean Pro v6.21多国语言版
  5. matlab lu解线性方程,LU分解和求解线性方程组
  6. 使用jwplayer插件播放视频
  7. 第一章 极限、连续与求极限的方法
  8. python数据拟合方法_Python-最小二乘法曲线拟合
  9. 一款全面超越ps3的国产游戏机
  10. 遥感资源大放送(下)| 11 个经典遥感数据集