序幕
1.什么是RxJava?
RxJava是一个针对于Java语言的一个异步的响应式编程库。
核心是【异步】!!!
2.RxJava的特性
- 事件变换
- 链式调用
- 观察者模式
- 异常传递
- 线程控制
核心特性是【事件变换】!!!
在事件变换的基础上,才有了其它的特性!
最大的用处就是像调用同步的方法那样去调用异步的方法。
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>:
- 如果存在方法:T -> R,则使用map
- 如果存在方法:T -> AsynWork< R>,则使用flatMap
但是,现在的代码显然还是没有最初的同步方式简洁,所以,是时候玩Lambda表达式!
准备开始Lambda表达式:
Lambda表达式说明:
- Lambda表达式可以认为是匿名方法,左边是形参,右边是方法体,一般用于接口回调的实现
- 特别注意:这里接口中不要有相同入参、相同出参的方法,哪怕方法名不同也不行,因为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升级篇