RxAndroid使用初探—简洁、优雅、高效

引言

RxAndroid是一个开发库、是一种代码风格、也是一种思维方式。

正如标题所言,RxAndroid的特点是简洁、优雅、高效,它的优点是多线程切换简单、数据变换容易、代码简洁可读性好、第三方支持丰富易于开发;缺点是学习成本较高、出错难以排查。

本文参考了“扔物线”的文章:给 Android 开发者的 RxJava 详解

本文参考了“C.L. Wang”的文章:详细解析 RxAndroid 的使用方式

本文参考了“张磊BaronZhang”的系列文章:RxJava系列

本文参考了“small-dream”的文章:Retrofit基本使用教程

用途与优势

·起源

RxAndroid源于RxJava——"a library for composing asynchronous and event-based programs using observable sequences for the Java VM“,意为“一个在Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库”。

·用途

RxAndoid在我的粗略理解中,是一个实现异步操作的库,具有简洁的链式代码,提供强大的数据变换。

详细解析 RxAndroid 的使用方式中,有这样一个例子:

一个典型的Android图文列表

这个界面是一个典型的Android图文列表,取在线数据,并显示为图文列表,业务流程是这样的:

1.输入多个人名

2.根据人名,在线查询对应的信息

3.获取返回信息,显示为图文列表

我们在常规开发中,一般要做到这样几件事:

•用异步线程在线查询数据

•用foreach查询每个用户(没有批量查询的接口)

•访问网络,查询每个人名对应的github信息(返回Json字符串)

•类型转换(Json->Java Object)

•在主线程中接收数据

•每收到一条数据,就用Adapter向列表添加一条,并刷新界面

实现这样的需求,可以说功能不多,代码不少,少说也得N个类+几百行代码吧,加班到23:59:59应该就能搞定了…工作量主要在异步网络操作、类型转换和界面刷新上。

不过,RxAndroid可以做到,用区区一小段儿代码就实现这个需求:

一段典型Rx代码

除了MainActivity、layout文件和Adapter类之外,主要的业务逻辑都在这一段儿代码中实现了。感兴趣的朋友可以在详细解析 RxAndroid 的使用方式中找到这段儿代码。

刚才谁说要几百行代码来着...

·优势

我看到这段代码,想到的第一件事就是异步好简单、代码好简洁,一个简单、一个简洁,这就意味着工作效率。

知识结构

前面说过,RxAndroid学习成本比较高,其中一个原因是出现的概念和知识点比较多,我个人倾向于把它们分为三部分:基本概念、线程控制和变换操作。

��·基本概念

先看一下刚才那段儿RxAndroid代码,里面有这么几个元素:

RxAndriod代码中的元素

图中出现的基本概念,包括观察者、被观察者和订阅、另外还有一个图中没有出现的事件。

线程控制和变换操作我们先略过,在后面再看。

我们理解一个观察者、被观察者、订阅和事件的关系:


被观察者、观察者、订阅和事件

这显然是一个观察者模式,其中有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。

与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() / onEvent())之外,还定义了两个特殊的事件:onCompleted() 和 onError(),作为事件队列的结束。

·基本概念—被观察者Observable


被观察者

被观察者有这么几个特点:

•每一段Rx代码都从被观察者开始

•被观察者向观察者抛出onNext,onCompleted,onError事件,onNext可以连续多个,但onCompleted/onError只能出现一次,且出现后就结束事件队列

•用一些操作符可以方便地生成Observable对象,例如这段代码等价于Observable.just("Hello","Hi","Aloha");

·基本概念—观察者Observer及其变体


观察者

观察者有这么几个特点:

•被观察者抛出事件,观察者响应事件

•观察者决定如何响应onNext/onCompleted/onError事件

订阅(.subscribe(...))的逻辑和写法是有问题的,从逻辑上是观察者订阅被观察者,但写法却好像是被观察者订阅了观察者,这是为了不打断Rx的链式代码格式,而且,我个人觉得写成.subscribeby(...)更恰当

观察者Observer有多种变体的写法:

.subscribe(new Observer…)

.subscribe(new Subscriber…)

.subscribe(new onNextAction…,new onErrorAction…,new onCompleteAction…)

.subscribe(new onNextAction…,new onErrorAction…)

.subscribe(new onNextAction…)

其中,Observer是原始写法;

原始写法

Subscriber继承了Observer,是推荐写法;

推荐写法

也可以直接传对三个事件的响应;

直接响应三个事件

或者只响应两个事件、或者只响应一个事件。

只响应部分事件

ActionN和FuncN

我们注意到,上图的代码中出现了ActionN和FuncN的代码形式,这都是RxJava的接口:

ActionN,Action0/Action1/…ActionN是RxJava的接口,可传入0~N个参数,没有返回值;

FuncN,从Func0/Func1/…FuncN是RxJava的接口,可传入0~N个参数,有返回值。

到这里,其实我们就已经能读懂大部分的Rx代码了,包括最前面那段儿。

·基本概念—订阅、取消订阅、延迟

RxAndroid在订阅和取消订阅时也有丰富的处理函数:

•doOnSubscribe,在subscribe()调用后&事件发送前,执行,可以指定线程

observable.doOnSubscribe(…)

•doOnUnsubscribe,取消订阅时的监听

observable.doOnUnsubscribe(…)

•doOnCompleted,正常结束时的监听

observable.doOnCompleted(…)

•doOnError,出错时的监听

observable.doOnError(…)

•doOnTerminate,即将被停止(正常/异常)监听

observable.doOnTerminate(…)

•finallyDo,结束(正常/异常)监听

observable.finallyDo(…)

•delay,延迟一段儿时间再发射(已运算)

observable.from(items).delay(5,TimeUnit.SECONDS).observeOn(Schedulers.newThread()).subscribeOn(Schedulers.newThread());

•delaySubscription,延迟onNext事件

observable.delaySubscription(2,TimeUnit.SECONDS)

•timeInterval,定制发射事件

observable.interval(1,TimeUnit.SECONDS).take(5).timeInterval();

•要及时释放订阅,否则可能造成内存泄漏

·基本概念—事件

就是刷一下事件这个概念的存在感,本节无内容...

·线程控制

RxAndroid的一个突出优势是多线程控制非常简单。

·线程控制—跨线程

和以往代码繁琐的多线程控制不同,RxAndroid可以轻松地用一行代码来切换线程

切换线程

两个函数:

•subscribeOn()指定事件产生的线程(为前面的代码指定线程)

•observeOn()指定事件消费的线程(为后面的代码指定线程)

�多钟线程:

•Schedulers.immediate,默认,当前线程,立即执行

•Schedulers.trampoline,当前线程,要等其他任务先执行

•Schedulers.newThread,启用一个新线程

•Schedulers.io,擅长读写文件、数据库、网络信息,一个无数量上限的线程池

•Schedulers.computation,擅长cpu密集型计算,固定线程池,不要做io操作,会浪费cpu

•AndroidSchedulers.mainThread,Android主线程

·线程控制—如何操作

我们举个栗子,说明如何灵活地切换线程

多钟切换线程

需要注意的是:

•subscribeOn只能定义一次,除非是在定义doOnSubscribe

•observeOn可以定义多次,决定后续代码所在的线程

·线程控制—安全性

只要涉及到多线程,就一定要考虑安全性问题,这方面,我个人主要考虑3种方法

使用Rx缓存:

缓存

被观察者可以使用Rx缓存,对同一个被观察者对象执行订阅或取消订阅时,这个被缓存的Observable不会去重复查询数据,它会按自己的节奏去继续执行网络请求,你需要在生命周期之外去做存储。

使用RxLifecycle:

与Activity或Fragment绑定生命周期


在指定生命周期事件中解除订阅

及时取消订阅:

使用CompositeSubscription去收集所有Rx的Subscriptions,统一取消订阅

·变换操作

除了前面讲的多线程操作简单,Rx还有一个深受广大群众喜爱的特点,就是强大的数据变换能力。

所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列

一个简单的例子:

一个变换的例子

实际上,变换有两大类,一类是针对事件/数据的lift变换,另一类是针对Observable本身的compose变换:

变换的分类

·变换操作—lift变换

我们先看三个典型的lift变换函数:

flatmap

flatmap函数,可以把把一组String转换为一组User数据对象。

throttleFirst

throttlefirst函数,在连续点击时,在两秒内只响应第一个点击事件。

merge

merge函数,可以把两组数据合并为一组数据。

上述3个函数,代表三种lift变换操作符:

转换操作符:能实现一对一转换、一对多转换、依次将输出作为输入、分组输出 等

常用函数包括 map flatMap concatMap flatMapIterable switchMap scan groupBy ...

过滤操作符:能实现去重、取前N个、取后N个、取第N个、从第N个取、自定义过滤 等

常用函数包括 fileter take takeLast takeUntil distinct distinctUntilChanged skip skipLast ...

组合操作符:能实现数据合并、数据运算、数据排列、组合配对 等

常用函数包括 merge zip join combineLatest and/when/then switch startSwitch...

·变换操作—compose变换

compose变换不针对事件和序列,针对的是Observable本身,可以理解为,compose是定义一组变换模板,让多个Observable执行同样的变换

变换操作

一个典型的例子是,用compose函数实现与RxActivity生命周期的绑定:

绑定生命周期

注意,这里绑定的是RxActivity的生命周期,Activity本身是没有这个功能的。

·回顾一下lift和compose变换的区别

lift和compose的区别

lift变换,针对事件项和事件序列

lift

lift的功能在于变换数据,定义如何响应事件:

·变换、如String->Bitmap;

·过滤、如只处理前三条;

·合并、如把两组数据合并为一组

compose变换,针对Observable

compose

compose的功能在于定义被观察者的行为:

定义一组变换模板,让多个Observable实现同一组变换

如:设置Observable的生命周期

变换是RxJava中非常重要,也比较复杂的一部分内容,这里只是做了简单的归纳和介绍,建议仔细阅读给 Android 开发者的 RxJava 详解RxJava系列,对变换建立更深入更全面的了解。

·回顾知识结构

基本概念里,我们主要了解了基于事件订阅的开发风格

线程控制里,我们了解到如何简洁高效地操作异步线程,决定每一段儿代码运行的线程

�变换操作里,我们了解到RxAndroid如何使用强大的数据查询/变换/使用

相关开发库

�在使用RxAndroid的开发中,可选择很多Rx特性的开发库

Lambda表达式

Java8的特性,可以进一步简化代码,但暂无Android Studio支持,需要retrolambda插件,有兼容性问题,可读性和可维护性差,风险高,目前不建议使用

RxBinding

处理控件的UI事件,提供一些高级事件,普通如点击、长按、勾选,高级如防抖(throttleFirst,N秒内只允许点击一次)、等待(debounce,一定时间内没有操作就触发事件)

RxLifecycle

设置生命周期,如自动跟随Activity或Fragment的生命周期去创建/销毁,不用自己写线程,但要使用Rx的Activity和Fragment

Retrofit

最匹配RxAndroid的网络访问框架,支持Rx的链式调用,可直接返回数据对象,自定义特性也非常强大

SqlBrite

帮你用Rx的方式完成数据查询,但它不是一个数据库框架

·相关开发库—Labmbda表达式

lambda

Lambda与Rx融合得更好,能使代码更简洁(可是…再等等吧)

·相关开发库—RxBinding

RxBinding

实质是用Observable去包装UI事件,然后就可以用RxJava的各种转换功能去扩展UI事件

�·相关开发库—RxLifecycle

RxLifecycle

通过compose操作符调用,完成Observable和当前的UI组件绑定,实现生命周期同步。当UI组件生命周期结束时,就自动取消对Observable订阅。

bindToLifecycle的生命周期与调用的位置有关:

RxLifecycle的生命周期

��·相关开发库—Retrofit

一段儿常规的Retrofit代码,包括接口文件、初始化和调用:

Retrofit

在这个例子中,Retrofit拼url的过程如下:

•定义基础url:"https://api.github.com/"

•定义@GET的参数:"users/{user}"

•定义函数的入参:@Path("user") String username

•基础url+@GET中的url:"https://api.github.com/users/{user}“(如果@GET的参数是另一个url如http://www.google.com/users/{user},就可以替换掉原来的基础url)

•根据参数@Path的传值,更换user的值:"https://api.github.com/users/jake“

Tips: 基础url不能写成"https://api.github.com"(必须以“/”结尾)

上述代码是void函数,用callback返回数据,Retrofit还可以直接返回Observable对象:

Retrofit返回Observable对象

上述例子均为GET方式,Retrofit还可以向服务器提交POST数据:

Retrofit的POST

因为POST提交Json需要加消息头"Content-type","application/json“,因为Retrofit默认使用的是okhttp这个网络框架,所以需要设置okhttp的拦截器Intercepter,拦截http请求进行监控,重写或重试:

拦截器

下面大概列举一下Retrofit各种网络操作的写法:

Retrofit的各种弄网络操作

需要注意的是,Retrofit使用okhttp实现网络访问,而okhttp在Android主线程执行时会报错,所以要注意网络操作所在的线程。

建议阅读Retrofit基本使用教程,详细了解一下Retrofit这个网络框架

��·相关开发库—SqlBrite

SqlBrite

如上图所示,SqlBrite使用Rx的订阅模式来查询 ,而不是直接执行查询,SqlBrite不是一个数据库框架,他依然需要让你自己写sql语句来创建数据库查询,就是查询的等操作,采用了RX的方式来完成,这样的好处,就是我们在展示列表的时候,增删改查后,Sqlbrite都会自动的调用查询,来改变你传入列表的数据。

实例分析

·实例分析-改造目标

我手头有一个项目,准备用RxAndroid进行改造,并评估改造效果,我希望做到以下几点:

•改造一个自定义控件,使用Rx的Interval来替换线程,使用RxLifeCycle把取消订阅托管给UI组件

•把原来的多层数据访问及处理改用Rx的链式方式实现

•用Retrofit实现Rx风格的网络访问

•保留MVP模式

•简化代码,增强代码可读性

·实例分析-环境准备

添加gradle引用:

compile'io.reactivex:rxandroid:1.1.0' //

RxAndroid

compile 'io.reactivex:rxjava:1.1.0' //推荐同时加载RxJava

compile'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理

compile'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析库

compile'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson库

compile 'com.trello:rxlifecycle:0.4.0' //RxLifecycle管理Rx的生命周期

compile'com.trello:rxlifecycle-components:0.4.0' // RxLifecycle组件

// RxBinding

compile'com.jakewharton.rxbinding:rxbinding:0.3.0'

compile'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0'

compile'com.jakewharton.rxbinding:rxbinding-design:0.3.0'

·实例分析-一个自定义控件

我有一个自定义的播放控件,功能如下:

自定义控件

准备使用Rx的Interval来替换线程,使用RxLifeCycle把取消订阅托管给UI组件

改造前后的代码如下:

用Rx的Interval代替原有的线程操作

线程控制

用RxLifeCycle来实现与RxActivity的生命周期同步

生命周期控制

改造后的效果:

•工作量大为减少

•不需要复杂的自定义线程

•代码变简洁易读,没有了迷之缩进,业务逻辑呈链式

•提高了可维护性

·实例分析-多层数据访问

原有的数据访问框架参考的google开源的mvp,结构如下:

数据层结构

改造前的具体代码如下:

改造前的数据层代码

改造后的代码如下:

改造后的数据层代码

主要变化有:

•不使用callback,改为返回Observable,可以与UI层的Rx无缝对接

•网络访问直接走Retrofit,代码更简洁

•线程管理更精细,网络操作在io线程上,运算操作在computation线程上

•主要逻辑在同一段链式代码中,提高了代码可读性

•RxJava有强大的操作符,可以进一步简化代码

上述代码虽然减少了类文件的数量,也简化了代码,但结构上还是略显繁琐,可以� 进一步简化,同时保留cache--local--net的三级数据访问结构:


进一步简化数据层

使用concat+first变换操作符,按照缓存->数据库->网络的顺序依次查询数据

concat()操作符可以持有多个Observable对象,并将它们按顺序串联成队列

first()操作符可以从多个数据源中,只检索和发送第一个返回的结果,并停止后续的队列

这两个操作符组合使用,可以实现逐级访问三级缓存,并更新和保存数据的功能。

·实例分析-在MVP中使用RxAndroid

我原来的项目结构参考了google的mvp项目,大量的callback调用其实与RxAndroid的链式调用是不兼容的,但经过对mvp和RxAndroid各自特点的比较后,我还是决定把他们融合在一起使用。

原因:

•mvp可以较好的剥离和复用业务逻辑,但是付出的代价是代码量和文件数量的增加

•Rx的优势在于异步和简洁,异步不是mvp的强项,简洁正好可以解决mvp代码庞大的问题

•Rx+mvp可以各取所长,不过mvp一般是通过回调来处理异步,在代码编写上需要转换风格

•在框架上要使用mvp的分层分工思想,而在具体的逻辑实现上,使用Rx的链式代码去做数据的查询、处理、显示

在具体操作上,使用了两种修改形式:

一种部分修改是,修改Presenter中的刷新函数,从回调改为RxAndroid:

修改Presenter中的业务实现

另一种修改是,放弃复杂的MVP文件结构,全部在Fragment中实现:

只保留Fragment

采用只保留Fragment的方式,看起来比起mvp方式是个倒退,但其实这反而是最合适的做法,事实上,某些业务场景下,使用mvp本来就是过度设计了,我在这个问题上的考虑如下:

•首先考虑业务情况,复用的是Fragment,而不是Presenter,那就只保留Fragment

•Presenter层的异步线程可以在Fragment中用RxLifecycle替代时,也不必须在Presenter中实现异步线程

•数据层只做网络数据访问,并做数据转换,完全可以用Retrofit替代

•生命周期全部托管至RxFragment,能减少一部分工作量并消除这部分风险

事情证明,这样改造后,文件减少、代码减少、可读性好、可维护性高

我个人对这个项目的改造持肯定态度,我相信深入学习RxAndroid后,还有进一步优化的空间

几点建议

•Rx涉及的陌生概念比较多,前期上手有一定障碍,要多动手去试

•Rx的功能非常多,这里只讲了冰山一角,建议先系统地了解一下RxJava

•要用事件订阅的思路做开发,和基于回调的思路有很多区别,要慢慢体会

•Rx的链式代码比较特殊,和其他形式的代码不能混搭,所以同一个业务模块要同时使用Rx,否则工作难以衔接

•还是因为代码形式不能混搭,旧项目要改Rx的话,也会遇到需要一口气打通整条逻辑链路的问题

•推荐再去仔细阅读本文的几篇参考文章:给 Android 开发者的 RxJava 详解详细解析 RxAndroid 的使用方式RxJava系列Retrofit基本使用教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容