目录
前言
之前两篇文章介绍了RxJava常用的操作符,本文将从实际开发着手,来聊聊RxJava的应用场景。再聊这个之前,先回顾一下RxJava的操作符:
如上图所示,RxJava基本分为这6类操作符,下面针对这6类操作符,具体介绍一下应用场景。
使用场景
场景1:interval周期性任务
适用于每隔一段时间执行某项任务,比如轮询请求,如果执行指定数量的次数可以用intervalRange,一次可以用timer。
// interval() 隔一段事件发送事件 从0开始无限+1
// 第1个参数第一次延迟时间
// 第2个参数 间隔时间
// 第3个参数 时间单位
Observable.interval(2,1,TimeUnit.SECONDS)
.subscribe(new Observer<Long>() {
// do something
});
场景2:网络嵌套请求
我们在平时开发过程应该遇到过,就是一个请求依赖上个请求的结果,对于这种嵌套请求,如果我们不用RxJava,往往代码写的不够优雅。
// 假设一个需求,一个新用户注册账号,注册成功后直接登陆账号,注册失败提示用户
// 一般的写法(伪代码)
// register方法
private void register(){
api.register(registerBean)
.subscribeOn(Schedulers.io()) // 在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 回到主线程去处理请求结果
.subscribe(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
login(); // 注册成功, 调用登录的方法
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 提示注册失败
}
});
}
private void login(){
api.login(LoginBean)
.subscribeOn(Schedulers.io()) // 在IO线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 回到主线程去处理请求结果
.subscribe(new Consumer<LoginResponse>() {
@Override
public void accept(LoginResponse response) throws Exception {
// 登陆成功,跳转主页面
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 提示登陆失败
}
});
}
如果我们用RxJava实现如下:
// FlatMap 将被观察者发送的事件,经过合并、拆分或者单独转换生成一个新的事件序列再发送
// 新合成的事件和原事件发送顺序无关
// 伪代码
Observable<RegisterBean> observable1 = Api.register();
Observable<LoginBean> observable2 = Api.login();
observable1.subscribeOn(Schedulers.io()) // io线程请求网络
.observeOn(AndroidSchedulers.mainThread()) // 请求完成回调主线程查看结果
.subscribe(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse response) throws Exception {
// 注册成功
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 注册失败
}
}).observeOn(Schedulers.io()) // 新被观察者切换io线程请求网络
// 作变换操作将注册请求转成登陆请求
.flatMap(new Function<RegisterBean, ObservableSource<LoginBean>>() {
@Override
public ObservableSource<LoginBean> apply(RegisterBean result) throws Exception {
return observable2;
}
})
// 请求结果切换到主线程展示
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Translation2>() {
@Override
public void accept(Translation2 result) throws Exception {
// 登陆成功
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// 登陆失败
}
});
我们从上面代码,可以看出,如果是嵌套请求,RxJava将第一个请求直接变换成第二个请求,从上到下,成功失败是不是相比之前的嵌套回调,要更一目了然。
场景3:合并数据源展示
例如我们展示数据时,需要用到两组数据展示,本地+网络合并显示,使用merge操作符:
// 网络获取
Observable<String> network = Observable.just("网络");
// 本地获取
Observable<String> file = Observable.just("本地文件");
Observable.merge(network, file)
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
// 接收数据
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
// 合并数据统一展示
}
});
场景4:缓存处理
我们经常在开发中会遇到一个问题,就是我们获取数据时,一般先从内存缓存获取,再从磁盘缓存处理,最后在执行网络请求获取,这里我们用到concat+firstElement(第一个有效数据)操作符。
Observable<String> memory = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Exception {
if (memoryCache != null) {
emitter.onNext(memoryCache);
} else {
emitter.onComplete();
}
}
});
Observable<String> disk = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Exception {
if (diskCache != null) {
emitter.onNext(diskCache);
} else {
emitter.onComplete();
}
}
});
Observable<String> network = Observable.just("network");
// 依次检查memory、disk、network
Observable.concat(memory, disk, network)
.firstElement()
.subscribeOn(Schedulers.newThread())
.subscribe({
// 结果展示
});
场景5:联合判断
适用于我们有个表格多个数据需要输入,才能提交,使用CombineLatest操作符。
// 跳过第一次输入
Observable<CharSequence> nameObservable = RxTextView.textChanges(name).skip(1);
Observable<CharSequence> ageObservable = RxTextView.textChanges(age).skip(1);
Observable<CharSequence> idObservable = RxTextView.textChanges(id).skip(1);
Observable.combineLatest(nameObservable, ageObservable, idObservable, new Function3<CharSequence, CharSequence, CharSequence, Boolean>() {
@NonNull
@Override
public Boolean apply(@NonNull CharSequence charSequence, @NonNull CharSequence charSequence2, @NonNull CharSequence charSequence3) throws Exception {
// 1. 姓名信息
boolean isUserNameValid = !TextUtils.isEmpty(name.getText());
// 2. 年龄信息
boolean isUserAgeValid = !TextUtils.isEmpty(age.getText());
// 3. 身份证信息
boolean isUserIdValid = !TextUtils.isEmpty(id.getText());
return isUserNameValid && isUserAgeValid && isUserIdValid;
}
}).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
// 最后判断是否可以提交
}
});
场景6:重试机制
例如我们执行某个请求或操作时,需要在一定的时间重试几次,最后再返回结果,这里用到repeatWhen操作符。
// 模拟网络请求或其他操作
Observable networkObservable = Observable.just("network") ;
networkObservable
.repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Observable<Object> objectObservable) throws Exception {
return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Object o) throws Exception {
// return Observable.empty(); // 发送complete事件 但观察者onComplete不会回调
// return Observable.error(new Throwable("出错了!!!"));
// return Observable.just(1);
// 加入判断条件:当轮询次数 = 4次后,就停止轮询
if (i > 2) {
// 此处选择发送onError事件以结束轮询,因为可触发下游观察者的onError()方法回调
return Observable.error(new Throwable("轮询结束"));
}
// 注:此处加入了delay操作符,作用 = 延迟一段时间发送(此处设置 = 1s),以实现轮询间间隔设置
return Observable.just(1).delay(1000, TimeUnit.MILLISECONDS);
}
});
}
}).subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "收到onNext事件: " + integer);
i++;
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "收到onError事件: " + e.toString());
}
@Override
public void onComplete() {
Log.d(TAG, "收到onComplete事件");
}
});
从上面代码可以看出,repeatWhen相当于有条件重试,我们可以根据实际需求改成对应的重试条件和重试间隔。
场景7:防抖机制
例如我们按钮点击的时候经常会遇到,短时间重复点击的问题,这里用到throttleFirst操作符,一段时间内只响应第一次事件。
RxView.clicks(btn)
.throttleFirst(1,TimeUnit.SECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
// 处理点击事件
}
});
场景8:联想搜索
例如我们在输入框输入文字时,联想搜索,为了减少不必要的网络请求,我们会在一段时间间隔过滤事件,这里用到debounce操作符。
RxTextView
.textChanges(name)
// skip(1)跳过第一次数据为空事件,debounce 1秒中只执行一次
.debounce(1,TimeUnit.SECONDS).skip(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<CharSequence>() {
@Override
public void accept(CharSequence charSequence) throws Exception {
// 服务器请求的数据
}
});
上面的代码,简单来说就是,除去第一次输入框文字为空,输入框文字1秒钟没有发生变化才请求网络搜索。
场景9:背压
先说说背压是什么吧:
背压:被观察者发送事件速度和观察者接收事件速度不匹配,观察者内会创建一个无限制大小的缓冲池存储未接收的事件,存储事件越来越多时造成OOM的现象,称为背压。(背压通常发生在被观察者和观察者异步订阅即subscribeOn与observeOn不为同一个线程时,被观察者与观察者内存在不同时长耗时任务)
为了解决这种情况的策略称为背压策略(Flowable)。
先介绍一下背压策略种类(BackpressureStrategy):
- ERROR:当被观察者发送事件大于128(Flowable默认缓存是128)时,观察者抛出异常并终止接收事件,但不会影响被观察者继续发送事件。
- BUFFER:与Observable一样存在背压问题,但是接收性能比Observable低,因为BUFFER类型通过BufferAsyncEmitter添加了额外的逻辑处理,再发送至观察者。
- DROP:每当观察者接收128事件之后,就会丢弃部分事件。
- LATEST:LATEST与DROP使用效果一样,但LATEST会保证能接收最后一个事件,而DROP则不会保证。
- MISSING:MISSING就是没有采取背压策略的类型,效果跟Obserable一样。
介绍策略我们看下出现背压问题和解决的代码:
// 模拟出现背压情况
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
int i = 0;
while (true) {
Thread.sleep(500);
i++;
e.onNext(i);
Log.i(TAG, "每500ms发送一次数据:" + i);
}
}
}).subscribeOn(Schedulers.newThread())// 使被观察者存在独立的线程执行
.observeOn(Schedulers.newThread())// 使观察者存在独立的线程执行
.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Thread.sleep(5000);
Log.e(TAG, "每5000ms接收一次数据:" + integer);
}
});
// 使用Flowable BackpressureStrategy.ERROR策略解决背压问题
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
for (int j = 0; j <= 150; j++) {
e.onNext(j);
Log.i(TAG, " 发送数据:" + j);
try {
Thread.sleep(50);
} catch (Exception ex) {
}
}
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE); // 观察者设置接收事件的数量,如果不设置接收不到事件
}
@Override
public void onNext(Integer integer) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "onNext : " + (integer));
}
@Override
public void onError(Throwable t) {
Log.e(TAG, "onError : " + t.toString());
}
@Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});
从上面代码代码可以看出Flowable和Observable差不多,主要是创建增加了背压策略参数,被观察者、观察者实现类有所区别。
上面介绍背压策略MISSING是没有采取背压策略但是结合onBackPressure操作符达到同样的效果:
Flowable.interval(50,TimeUnit.MILLISECONDS)
.onBackpressureDrop() // 效果与Drop类型一样
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG,"onNext : "+(aLong));
}
});
onBackPressure操作符主要包括以下三种类型:
- onBackpressureBuffer :与BUFFER类型一样效果。
- onBackpressureDrop :与DROP类型一样效果。
- onBackpressureLaster :与LASTER类型一样效果。
背压策略说完了,最后讲讲request(long count)、requested(long count)这两个方法,前者最大接收数量、后者剩余可接收数量:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
for(int j = 0;j<15;j++){
e.onNext(j);
Log.i(TAG,e.requested()+" 发送数据:"+j); // 剩余可接收数量
try{
Thread.sleep(50);
}catch (Exception ex){
}
}
}
},BackpressureStrategy.BUFFER)
// .subscribeOn(Schedulers.newThread())
// .observeOn(Schedulers.newThread())
.subscribe(new Subscriber<Integer>() {
private Subscription subscription;
@Override
public void onSubscribe(Subscription s) {
subscription = s;
s.request(10); // 观察者设置接收事件的数量,如果不设置接收不到事件
}
@Override
public void onNext(Integer integer) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG,"onNext : "+(integer));
}
@Override
public void onError(Throwable t) {
Log.e(TAG,"onError : "+t.toString());
}
@Override
public void onComplete() {
Log.e(TAG,"onComplete");
}
});
背压相关策略、操作符、方法大概介绍完了,如果想深入了解背压原理,可以看下这篇文章:Android RxJava :图文详解 背压策略
场景10:结合其他框架一起使用
RxJava结合其他框架使用大概有以下几种:
- RxJava+Retrofit实现网络请求
- Rxbinding 控件的点击、长按、EditText输入文本变化等监听处理
- Rxbus 和EventBus类似组件通信库,基于RxJava Subject同时承担订阅者、被订阅者角色实现的
第一个就不介绍了,我们在开发中应该经常遇到,主要介绍一下后面两个。
RxBinding我们在之前已经介绍过结合RxJava操作符实现防抖、联合判断、联想搜索功能,看下长按事件:
RxView.longClicks(mLoginButton)
.subscribe(new Consumer<Unit>() {
@Override
public void accept(Unit unit) throws Exception {
Log.i(TAG, "accept: longClicks-test");
}
});
最后说一下Rxbus,看下代码:
// 注册消息
registerRxBus(RxTestBean.class, bean -> {
if (bean != null) {
textView.setText(bean.getName() + "-" + bean.getAge());
}
});
// 注册
public <T> void registerRxBus(Class<T> eventType, Consumer<T> action) {
Disposable disposable = rxBus.doSubscribe(eventType, action, throwable -> Log.e("rxbus", throwable.toString()));
rxBus.addSubscription(this, disposable);
}
// ondestroy解绑
RxBus.getIntance().unSubscribe(this);
// 发送消息
RxBus.getIntance().post(new RxTestBean("张三", 28))
从上面代码可以看出,和EventBus用法基本类似。
总结
最后总结一下,RxJava在我们平常开发场景还挺多的,当然这些场景不用RxJava也可以实现,但是RxJava实现的方式相比传统的方式要优秀不少,而且多一种解决方案,不相当于手里多一张牌吗,其实这些应用场景也不用死记硬背,有个印象,真正遇到问题时,回顾一下,如果能够解决你遇到的问题不是挺好的吗。最后一张图回顾一下:
Thanks:
代码地址:RxJavaLearn
创作不易,如果觉得有用的话,麻烦点个赞。