自定义RxJava之旅

序幕

1.什么是RxJava?

RxJava是一个针对于Java语言的一个异步的响应式编程库。
核心是【异步】!!!

2.RxJava的特性

  1. 事件变换
  2. 链式调用
  3. 观察者模式
  4. 异常传递
  5. 线程控制

核心特性是【事件变换】!!!
在事件变换的基础上,才有了其它的特性!

最大的用处就是像调用同步的方法那样去调用异步的方法

3.RxJava有什么用?

如果有这样的需求:获取指定标签的新闻列表,得到其中最新的新闻,保存它并返回Uri,你会怎么写?

注意:getNewsList和save都是耗时操作哦!

你希望可以这样:

这里写图片描述

但是,为了避免【阻塞主线程】,你不得不写这样的迷之缩进

这里写图片描述

好吧,现在确实是不会阻塞主线程了,但是,这样的代码,你不觉得难以阅读么?

这还好只有两层回调,那要是来个四五层回调的嵌套,那代码,想想也是醉了,相信没有人愿意看四五层回调的代码!这就是传说中的【回调地狱】

如果用了RxJava的话,那你可以这样实现:

这里写图片描述

好吧,如果你说你没玩过Lambda表达式,那好,我们就不用Lambda表达式简写了,来点原始的:

这里写图片描述

看,这就是RxJava的好处:简洁,能把任何复杂逻辑都能串成一条线

现在,心动了吧?那么,从现在开始,我们一步步的自己实现一个简单版的RxJava吧!相信看完之后,大家对于RxJava一定可以豁然开朗!


准备开始自定义RxJava之旅:

还是上面的例子,让大家看看如何将一个回调嵌套变成链式调用!

需求:获取指定标签的新闻列表,得到其中最新的新闻,保存它的Uri

条件:现在我们有个第三方的API,可以获取指定标签的所有新闻列表,每条新闻包含时间和内容等,同时API也可以保存单条新闻得到一个URI地址。

补充:假设第三方提供的jar里面提供了Api和ApiImpl,不可再更改:
并且getNewsList耗时1.5秒,save耗时0.5秒。
这里写图片描述

这里写图片描述

自定义RxJava之旅

1.同步方式

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

这里的getNewsList和save都是耗时操作,会阻塞主线程,显然同步方式不适用!


准备开始异步回调方式:

因为原始的Api并没有给我们提供异步的调用方式,所以在正式开始之前,我们需要将原始的APi转换成异步的ApiAsync,所以我们添加了几个辅助类:ApiAsync、Callback

这里写图片描述

这里写图片描述

在ApiAsync中,我自己将api.getNewsList放在了一个子线程中,这样就不怕阻塞主线程了。这里,用到了一个自定义的线程池,不记得了的童鞋可以查看我的另一篇博文:Java-线程池


2.异步回调方式

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

这里我没有将最后得到的Uri切换回主线程,这涉及到线程间通讯的问题,详情可查看我的另一篇博文:自定义消息传递机制

由日志可知,【主线程阻塞】的问题解决了,获取新闻列表是在thread-1执行的,保存得到URI是在thread-2执行的,但是,新的问题又来了:【回调嵌套】
怎么办?耐心看!


准备开始最原始的异步任务方式

同样的,需要先引入几个辅助类:ApiWork、AsyncWork
这里写图片描述

这里写图片描述

这是一个很重要的封装!!!
这里,我们将异步操作抽象成一个对象。
我们看接口封装类ApiWork中的异步操作的特点:
1. 入参(tag、news),
2. 返回AsyncWork< T>,T为(List< News>、Uri)。
3. AsynWork内部实现了start方法,将回调传递给ApiAsync对象的(getNewsList、save)等方法。

再次说明:任何异步操作需要携带所需的常规参数和一个回调实例对象

通过将异步操作抽象成对象,以后我们就可以在异步任务的抽象类中定义一些常用的变换逻辑(RxJava称之为操作符,例如:map、flatMap等),这样,就可以避免在具体的异步任务中,要做一些变换时,又要写一大堆业务无关的模板代码!


3.异步任务方式-原始

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

在实现时,大家记住AsyncWork< T>只是一个异步任务的封装,只有调用了start之后,任务才真正开始执行!姑且可以认为start也是一个特殊的操作符:

start:
作用:AsyncWork T> -> T
说明:调用start方法,执行异步任务,通过接口回调,得到结果T

这里相比第2种异步方式,似乎更复杂了啊!回调还是有两层嵌套,而且还多绕了几个弯,还不如以前好懂了呢,这不是吃饱了撑着么?别急,接下来见分晓!


准备开始异步任务方式-事件变换

根据刚才的实现流程,我们知道异步任务的变换流程是:
1. AsyncWork< List< News>>:获取新闻列表的异步任务
2. AsyncWork< News>:获取最新新闻的异步任务
3. AsyncWork< Uri>:保存新闻得到URI的异步任务

(其实获取AsyncWork< News>并不是异步的操作,不过,不要曲解了我的异步任务的意思:
异步任务:是指可以支持异步操作,但并不是只支持异步操作)

将一个异步任务变换成另一个异步任务,这就是【事件变换】,这也是RxJava的核心特性!!!


4.异步任务方式-事件变换

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

这里,我们将三步操作,分成了三个异步任务,开始有了一点链式调用的影子了。

不过,在每个任务里面,还有着大量的业务无关的模板代码:
1. 获取AsyncWork< News>时,只有getLatestNews(newsList)是有用的!
2. 获取AsyncWork< Uri>时,只有apiWork.save(result)是有用的!

这显然还存在着很大的优化空间,是时候干掉那些模板代码了!


准备开始异步任务方式-操作符控制事件变换

将上一种实现方式中,获取AsyncWork< News>的逻辑泛型化,其实就是:
AsyncWork< T> -> AsyncWork< R>
(T指List< News>,R指News)

上面我们已经提到了:

将异步操作抽象成对象,以后我们就可以在异步任务的抽象类中定义一些常用的变换逻辑

这里的AsyncWork< T> -> AsyncWork< R> 显然就是一个常用的变换逻辑。

于是我们拓展一下我们的AsyncWork< T>抽象类!

定义一个map方法:
入参:T,R
出参:AsyncWork< R>

如何实现map方法呢?
先看我们上一种实现方式里面是如何实现AsyncWork< List< News>> 变换成
AsyncWork< News>的:
这里写图片描述

泛化一下就是:
这里写图片描述

这个castT2R的方法显然应该是抽象方法,不应该由自己实现的,于是,我们又引入了一个辅助类:
这里写图片描述

引用这个辅助类之后,我们的map就可以调整成:
这里写图片描述

好了!第一个操作符map搞定:

map
作用:AsyncWork< T> –> AsyncWork< R>
说明:Func接口的call方法中实现(T -> R)

于是上一种实现方式的第二步-获取AsyncWork< News>,就可以用map来简化了。
这里写图片描述

那么上一种实现方式的第三步-获取AsyncWork< Uri>,可以使用map来简化么?
这里写图片描述
在将News变换成Uri的时候,卡住了!!!
因为我们现有的api没有实现:News -> Uri
只有一个api(也就是save)实现了:News -> AsyncWrok< Uri>

这里我们就对map方法的使用条件也就更清楚了:存在方法:T -> R

既然map无法实现,那我们就再定义一个操作符吧!
于是我们再次拓展一下我们的AsyncWork< T>抽象类!

定义一个flatMap方法:
入参:T,R
出参:AsyncWork< R>
条件:存在方法:T -> AsyncWork< R>

如何实现flatMap方法呢?
老规矩,先看我们上一种实现方式里面是如何实现AsyncWork< News> 变换成
AsyncWork< Uri>的:
这里写图片描述

泛化一下就是:
这里写图片描述

同理,这个castT2WorkR的方法也应该是抽象方法,不应该由自己实现的,于是,我们引入了刚才的辅助类-类型变换泛型接口,调整后得到:
这里写图片描述


5.异步任务方式-操作符控制事件变换

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

现在我们再看,每个异步任务里面都没有嵌套的回调了,终于摆脱回调地狱了!
相信看了上面的代码之后,大家也都了解了map和flatMap的作用了:

想将AsyncWork< T>转成AsyncWork< R>:

  1. 如果存在方法:T -> R,则使用map
  2. 如果存在方法:T -> AsynWork< R>,则使用flatMap

但是,现在的代码显然还是没有最初的同步方式简洁,所以,是时候玩Lambda表达式!


准备开始Lambda表达式:

Lambda表达式说明:

  1. Lambda表达式可以认为是匿名方法,左边是形参,右边是方法体,一般用于接口回调的实现
  2. 特别注意:这里接口中不要有相同入参、相同出参的方法,哪怕方法名不同也不行,因为Lambda表达式是在编译时自动根据入参和出参来寻找方法的(存疑!)

一个简单的Lambda用例:
这里写图片描述

6.异步任务方式-Lambda简写事件变换

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述

现在的异步方式,是不是和最开始的同步方式,看起来很相似了啊,这就是RxJava的好处,像写同步的代码一样去写异步的代码!


7.异步任务方式-事件链式变换

实现代码:
这里写图片描述
测试代码:
这里写图片描述
输出日志:
这里写图片描述


好了,到这里,我们自定义的RxJava就完结了。下面我们来看看真正的RxJava是怎么玩这个例子的吧!

同样的,首先定义一个辅助类:ApiRx,将原始的Api的接口转成Rx方式的接口
这里写图片描述

辅助类搞定,下面正式开始!


8.RxJava方式-原始

实现代码:
这里写图片描述

测试代码:
这里写图片描述

输出日志:
这里写图片描述

9.RxJava方式-Lambda+链式

如果你偏爱链式调用,那也可以这样:

实现代码:
这里写图片描述

测试代码:
这里写图片描述

输出日志:
这里写图片描述


在最后,我们回顾一下:
首先,将我们的框架AsyncWork与RxJava对比:
1. AsyncWork = Observable 消息发射器
2. Callback = Subscriber 消息接受器
3. asynWork.start() = observable.subscribe()
4. 操作符:AsyncWork只实现了map和flatMap,不过经过上面的实现,自己再另外拓展一个操作符也未必是难事!

然后,我们审视下我们的框架,具备了以下的RxJava的特性:
1. 事件变换
2. 链式调用
3. 观察者模式
4. 异常传递

就是第5项-线程控制没有实现,不过,我觉得将线程控制框架和事件变换框架分开来,可能更好些!可以更自由的决定每个事件在哪个线程执行!

同时,我们会发现:如果一个发射器想将一个消息,发送给多个接收器,用AsyncWork是无能为力的,RxJava的话呢,也需要自己去拓展一个RxBus,感觉还是不如EventBus方便!下次有机会的话,争取再自定义一个EventBus吧,感觉这种模仿一个框架的学习方式,是最能领悟到原来框架的精髓的!

看了那么多的源码解析,但是如果没有自己去实现一次,就算知道了框架的大致结构,一些细节性的东西,还是不太明白的。

有的坑,只有自己亲自踩过,才会刻骨铭心!

所有代码均已上传到:Github,欢迎Star!

菜鸟一枚,水平有限,欢迎大家指出博文中的不足之处,小鱼将不胜感激!@qq:630709658


参考目录:
1. NotRxJava懒人专用指南
2. 给Android开发者的RxJava详解
3. 深入浅出RxJava
4. 用RxJava实现事件总线(Event Bus)
5. 用RxJava实现事件总线RxBus并实现同类型事件的区分
6. RxBus升级篇

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值