前言
之前在github开源过一个网络库RxEasyHttp,这是一款基于RxJava2+Retrofit2实现简单易用的网络请求框架。在这里对网络库的用法就不做过多介绍,感兴趣的可以去了解下。在使用过程中一些网友反馈不知道怎么结合Rxjava2来实现一些场景需求,希望能够写一遍文章介绍下。终于抽出时间来对与Rxjava2在实际场景使用的一些案例做个简单的总结和介绍。不知道怎么使用,主要是对RxEasyHttp有个误区,RxEasyHttp不仅是支持采用链式调用一点到底方便使用,同时也支持返回Observable
的用法,拿到了Observable
自然就可以很好的利用Rxjava操作符了来实现各种强大的功能。本文主要是讲RxEasyHttp与Rxjava2怎么结合的,也不会Rxjava2的操作符深入讲解,不然就脱离了本文的重心!废话不多说了,一起来看看是如何使用的。
场景介绍
场景一:延迟请求
在页面网络接口请求中,不是希望立马请求,而是需要延迟指定的时间后再去请求。
延迟请求:利用RxJava的timer
操作符。
timer:主要作用就是创建一个Observable
,它在一个给定的延迟后发射一个特殊的值,只是延迟发送一次并不会按照周期执行。
timer()
源码如下:
public static Observable timer(long delay, TimeUnit unit) {
return timer(delay, unit, Schedulers.computation());
}
可以看到采用timer()
返回的是Observable
,而网络请求返回的Observable
并不是Observable
,如何将这两个Observable
关联起来,就需要采用另外一个操作符flatMap()
,简单理解就是flatMap
使用一个指定的函数对原始Observable发射的每一项数据进行相应的变换操作。flatMap详细作用不做过多介绍。
例如:延迟5秒请求
//延迟5s请求
Observable.timer(5, TimeUnit.SECONDS).flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull Long aLong) throws Exception {
//延迟结束开始执行网络请求
Observable observable = EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
.timeStamp(true)
.execute(SkinTestResult.class);
return observable;
}
}).subscribe(new BaseSubscriber() {
@Override
protected void onStart() {
}
@Override
public void onError(ApiException e) {
showToast(e.getMessage());
}
@Override
public void onNext(@NonNull SkinTestResult skinTestResult) {
Log.i("test", "=====" + skinTestResult.toString());
}
});
//在不需要轮询的时候,取消轮询
//EasyHttp.cancelSubscription(polldisposable);
- timer在这里作用延迟5s结束时就会触发网络请求
- flatMap在这里的作用就是将
timer
操作符返回的Observable
和网络请求的Observable
做转换,在subscribe订阅时返回的内容,我们真正需要的SkinTestResult,而不是Long. 因此将Observable
变换成Observable
输出SkinTestResult,完美达到目的。
场景二:轮询请求-无限轮询
在项目中需要用到每隔5s刷新一次页面或者拉取最新消息。轮询器大家一定不陌生,开发中无论是Java的Timer+TimeTask , 还是Android的Hanlder都可实现,现在介绍另一种简单的实现方式。
无限轮询:利用RxJava的Interval
操作符。
interval:创建一个按固定时间间隔发射整数序列的Observable,它是按照周期执行的。源码如下(只展示相关的两个方法):
public static Observable interval(long initialDelay, long period, TimeUnit unit) {
return interval(initialDelay, period, unit, Schedulers.computation());
}
public static Observable interval(long period, TimeUnit unit) {
return interval(period, period, unit, Schedulers.computation());
}
可以看到采用interval()
返回的是Observable
,而网络请求返回的Observable
并不是Observable
,如何将这两个Observable
关联起来,就需要采用另外一个操作符flatMap()
,简单理解就是flatMap使用一个指定的函数对原始Observable发射的每一项数据之行相应的变换操作。flatMap详细作用不做过多介绍(同上场景一)。
例如:间隔5s轮询一次
//自己根据需要选择合适的interval方法
Disposable polldisposable = Observable.interval(0, 5, TimeUnit.SECONDS).flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull Long aLong) throws Exception {
return EasyHttp.get("/ajax.php")
.baseUrl("http://fy.iciba.com")
.params("a", "fy")
.params("f", "auto")
.params("t", "auto")
.params("w", "hello world")
//采用代理
.execute(new CallClazzProxy, Content>(Content.class) {
});
}
}).subscribeWith(new BaseSubscriber() {
@Override
public void onError(ApiException e) {
showToast(e.getMessage());
}
@Override
public void onNext(@NonNull Content content) {
showToast(content.toString());
}
});
//在不需要轮询的时候,取消轮询
//EasyHttp.cancelSubscription(polldisposable);
- interval在这里作用每隔5s结束时就会触发网络请求
- 注意
interval(0, 5, TimeUnit.SECONDS)
和interval(5, TimeUnit.SECONDS)
的区别,自己根据需要选择合适的interval方法。
interval(0,5, TimeUnit.SECONDS)
:3个参数,第一个参数表示初始化延时多久开始请求,这里用0表示不延时直接请求,第二个参数表示间隔多久轮询一次,这里表示间隔5s,第三个表示设置的时间单位。interval(5, TimeUnit.SECONDS)
:2个参数,其中的这个5就表示,初始延时5秒开始执行请求,轮询也是5s,第二个表示设置的时间单位,从上面提供的interval()
源码可以看出。
-
flatMap
在这里的作用就是将interval
的Observable
和网络请求的Observable
做转换,输出Content,而不是Long.
场景三:轮询请求-限定次数轮询
这个和无限轮询用法基本一样,只是多了轮询的次数限制条件,不是一直无限的轮询下去。
轮询次数:利用RxJava的intervalRange
或者take
操作符。
intervalRange:以一个例子说明可能更清楚,intervalRange(0,3,0,5, TimeUnit.SECONDS)
表示从0开始输出3个数据,延迟0秒执行,每隔5秒执行一次。
take:表示只取前n项。这里用take和interval操作符联合使用,由于一旦interval计时开始除了解绑就无法停止,使用take操作符就简单很多了,它的意思是只释放前n项,过后Observable流就自动终止。
例如:只轮询3次
int count = 3;//轮询3次
//方式一:采用intervalRange
//Observable.intervalRange(0,count,0,5, TimeUnit.SECONDS).flatMap(new Function>() {
//方式一:采用take
countdisposable = Observable.interval(0, 5, TimeUnit.SECONDS).take(count).flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull Long aLong) throws Exception {
return EasyHttp.get("/ajax.php")
.baseUrl("http://fy.iciba.com")
.params("a", "fy")
.params("f", "auto")
.params("t", "auto")
.params("w", "hello world")
//采用代理
.execute(new CallClazzProxy, Content>(Content.class) {
});
}
}).subscribeWith(new BaseSubscriber() {
@Override
public void onError(ApiException e) {
showToast(e.getMessage());
}
@Override
public void onNext(@NonNull Content content) {
showToast(content.toString());
}
});
//在不需要轮询的时候,取消轮询
//EasyHttp.cancelSubscription(polldisposable);
场景四:轮询请求-条件轮询
条件轮询和限定次数轮询比较像,都是起达到目的后终止轮询。比如一个网络请求一直在轮询执行,直到获取到了想要的内容后就终止掉轮询。
条件轮询:利用RxJava的takeUntil
操作符。
takeUntil:使用一个标志Observable是否发射数据来判断,当标志Observable没有发射数据时,正常发射数据,而一旦标志Observable发射过了数据则后面的数据都会被丢弃。
例如:轮询请求中如果返回的内容字符串中包含“示”就终止轮询
Observable.interval(0, 5, TimeUnit.SECONDS).flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull Long aLong) throws Exception {
return EasyHttp.get("/ajax.php")
.baseUrl("http://fy.iciba.com")
.params("a", "fy")
.params("f", "auto")
.params("t", "auto")
.params("w", "hello world")
//采用代理
.execute(new CallClazzProxy, Content>(Content.class) {
});
}
}).takeUntil(new Predicate() {
@Override
public boolean test(@NonNull Content content) throws Exception {
//如果条件满足,就会终止轮询,这里逻辑可以自己写
//结果为true,说明满足条件了,就不在轮询了
return content.getOut().contains("示");
}
}).subscribeWith(new BaseSubscriber() {
@Override
public void onError(ApiException e) {
showToast(e.getMessage());
}
@Override
public void onNext(@NonNull Content content) {
showToast(content.toString());
}
});
//在不需要轮询的时候,取消轮询
//EasyHttp.cancelSubscription(polldisposable);
场景五:轮询请求-过滤轮询
过滤轮询主要是指在轮询的过程中对订阅的内容做过滤,不是需要的内容就不会返回给订阅者,但是它不会中断轮询。过滤轮询也可以理解成是无限轮询加了一个过滤条件而已。
过滤轮询:利用Rxjava的filter
操作符。
filter:是对源Observable产生的结果按照指定条件进行过滤,只有满足条件的结果才会提交给订阅者。
例如:返回的状态码如果是错误就不返回给订阅者,不更新界面(只有保证每次请求成功才刷新界面),但是会继续轮询请求
Disposable filterdisposable = Observable.interval(0, 5, TimeUnit.SECONDS).flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull Long aLong) throws Exception {
return EasyHttp.get("/ajax.php")
.baseUrl("http://fy.iciba.com")
.params("a", "fy")
.params("f", "auto")
.params("t", "auto")
.params("w", "hello world")
//采用代理
.execute(new CallClazzProxy, Content>(Content.class) {
});
}
}).filter(new Predicate() {
@Override
public boolean test(@NonNull Content content) throws Exception {
//如果不满足条件就过滤该条轮询数据,但是轮询还是一直执行
//ErrNo==0表示成功,如果不等于0就认为失败,content不会返回给订阅者
return content.getErrNo() != 0;
}
}).subscribeWith(new BaseSubscriber() {
@Override
public void onError(ApiException e) {
showToast(e.getMessage());
}
@Override
public void onNext(@NonNull Content content) {
showToast(content.toString());
}
});
//在不需要轮询的时候,取消轮询
//EasyHttp.cancelSubscription(polldisposable);
-
filter
操作符在这里只是为了举列说明,是自己为了讲解定义了一个过滤轮询的概念,不是说filter
只能在轮询这里使用,它是可以和其它任何Rxjava操作符配合使用。
- 切记
takeUntil
和 filter
的区别,takeUntil
找到自己想要的数据后就结束了流,不再执行任何操作。filter
发现不符合条件的不会给订阅者,只有符合条件的才给订阅者,发现不符合的,不会中断操作。
场景六:嵌套请求
在开发中由于请求网络数据频繁,往往后面一个请求的参数是前面一个请求的结果,于是经常需要在前面一个请求的响应中去发送第二个请求,从而造成“请求嵌套”的问题。如果层次比较多,代码可读性和效率都是问题。嵌套请求:利用RxJava的flatMap
操作符。
flatMap:是一个Observable的操作符,接受一个Func1闭包,这个闭包的第一个函数是待操作的上一个数据流中的数据类型,第二个是这个flatMap操作完成后返回的数据类型的被封装的Observable。说白了就是将一个多级数列“拍扁”成了一个一级数列。
例如:网络请求获取到token后,把token当成另一个接口的参数,一个接口依赖另一个接口。
//第一个网络请求获取到token
Observable login = EasyHttp.post(ComParamContact.Login.PATH)
.params(ComParamContact.Login.ACCOUNT, "186****4275")
.params(ComParamContact.Login.PASSWORD, MD5.encrypt4login("123456", AppConstant.APP_SECRET))
.sign(true)
.timeStamp(true).execute(AuthModel.class);
login.flatMap(new Function>() {
@Override
public ObservableSource apply(@NonNull AuthModel authModel) throws Exception {
//获取到的token,给到第二个网络当参数。第二个网络开始请求
return EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
.params("accessToken", authModel.getAccessToken())//这个地方只是举例,并不一定是需要accessToken
.timeStamp(true)
.execute(SkinTestResult.class);
}
}).subscribe(new ProgressSubscriber(this, mProgressDialog) {
@Override
public void onError(ApiException e) {
super.onError(e);
showToast(e.getMessage());
}
@Override
public void onNext(SkinTestResult skinTestResult) {
showToast(skinTestResult.toString());
}
});
本例中只是展示了2个接口的嵌套请求,flatMap其实是可以支持嵌套很多个接口请求
场景七:合并请求(zip)
zip合并请求就是指当一个页面有多个不同的数据来源,既就是有多个不同的网络请求接口,等待这些所有的接口都请求完成后才返回给订阅者,刷新界面等操作。
zip:使用一个函数组合多个Observable发射的数据集合,然后再发射这个结果.
例如:一个页面有3个不同数据来源的网络请求接口,等待全部请求完成后才返回
Observable mobileObservable = EasyHttp.get("http://apis.juhe.cn/mobile/get")
.params("phone", "18688994275")
.params("dtype", "json")
.params("key", "5682c1f44a7f486e40f9720d6c97ffe4")
.execute(new CallClazzProxy, ResultBean>(ResultBean.class) {
});
Observable searchObservable = EasyHttp.get("/ajax.php")
.baseUrl("http://fy.iciba.com")
.params("a", "fy")
.params("f", "auto")
.params("t", "auto")
.params("w", "hello world")
//采用代理
.execute(new CallClazzProxy, Content>(Content.class) {
});
Observable> listObservable = EasyHttp.get("http://news-at.zhihu.com/api/3/sections")
.execute(new CallClazzProxy>, List>(new TypeToken>() {
}.getType()) {
});
//new Function3最后一个参数这里用的是List