原文链接:
Android RxJava操作符详解 系列:组合 / 合并操作符
Android RxJava 实战系列:从磁盘 / 内存缓存中 获取缓存数据
Android RxJava 实战讲解:合并数据源 & 同时展示数据
Android RxJava 实战系列:联合判断
目录
作用
组合多个被观察者&合并需要发送的事件
类型
应用场景&对应操作符 介绍
3.1 组合多个被观察者
concat()/concatArray()
- 作用:组合多个被观察者一起发送数据,合并后 按发送顺序串行执行
二者区别:组合被观察者的数量,即
concat()
组合被观察者数量≤4个,而concatArray()
则可>4个
- 具体使用
// concat():组合多个被观察者(≤4个)一起发送数据
// 注:串行执行
Observable.concat(Observable.just(1, 2, 3),
Observable.just(4, 5, 6),
Observable.just(7, 8, 9),
Observable.just(10, 11, 12))
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
// concatArray():组合多个被观察者一起发送数据(可>4个)
// 注:串行执行
Observable.concatArray(Observable.just(1, 2, 3),
Observable.just(4, 5, 6),
Observable.just(7, 8, 9),
Observable.just(10, 11, 12),
Observable.just(13, 14, 15))
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
merge()/mergeArray()
- 作用:组合多个被观察者一起发送事件,合并后 按时间线并行执行
Observable.merge(
Observable.intervalRange(0, 3, 1, 1, TimeUnit.SECONDS), // 从0开始发送、共发送3个数据、第1次事件延迟发送时间 = 1s、间隔时间 = 1s
Observable.intervalRange(2, 3, 1, 1, TimeUnit.SECONDS)) // 从2开始发送、共发送3个数据、第1次事件延迟发送时间 = 1s、间隔时间 = 1s
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Long value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
两个被观察者发送事件并行执行,输出结果 = 0,2 -> 1,3 -> 2,4
concatDelayError() / mergeDelayError()
a. 无使用concatDelayError()的情况
Observable.concat(
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onError(new NullPointerException()); // 发送Error事件,因为无使用concatDelayError,所以第2个Observable将不会发送事件
emitter.onComplete();
}
}),
Observable.just(4, 5, 6))
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
测试结果:第1个被观察者发送Error事件后,第2个被观察者则不会继续发送事件
<-- 使用了concatDelayError()的情况 -->
Observable.concatArrayDelayError(
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
emitter.onError(new NullPointerException()); // 发送Error事件,因为使用了concatDelayError,所以第2个Observable将会发送事件,等发送完毕后,再发送错误事件
emitter.onComplete();
}
}),
Observable.just(4, 5, 6))
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
测试结果:第1个被观察者的Error事件将在第2个被观察者发送完事件后再继续发送
3.2合并多个事件
该类型的操作符主要是对多个被观察者中的事件进行合并处理
Zip()
- 作用
合并 多个被观察者(Observable)发送的事件,生成一个新的事件序列(即组合过后的事件序列),并最终发送 - 特别注意
1.事件组合方式 = 严格按照原先事件序列 进行对位合并
2.最终合并的事件数量 = 多个被观察者(Observable)中数量最少的数量
<-- 创建第1个被观察者 -->
Observable observable1 = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
Log.d(TAG, "被观察者1发送了事件1");
emitter.onNext(1);
// 为了方便展示效果,所以在发送事件后加入2s的延迟
Thread.sleep(1000);
Log.d(TAG, "被观察者1发送了事件2");
emitter.onNext(2);
Thread.sleep(1000);
Log.d(TAG, "被观察者1发送了事件3");
emitter.onNext(3);
Thread.sleep(1000);
emitter.onComplete();
}
}).subscribeOn(Schedulers.io()); // 设置被观察者1在工作线程1中工作
<-- 创建第2个被观察者 -->
Observable observable2 = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
Log.d(TAG, "被观察者2发送了事件A");
emitter.onNext("A");
Thread.sleep(1000);
Log.d(TAG, "被观察者2发送了事件B");
emitter.onNext("B");
Thread.sleep(1000);
Log.d(TAG, "被观察者2发送了事件C");
emitter.onNext("C");
Thread.sleep(1000);
Log.d(TAG, "被观察者2发送了事件D");
emitter.onNext("D");
Thread.sleep(1000);
emitter.onComplete();
}
}).subscribeOn(Schedulers.newThread());// 设置被观察者2在工作线程2中工作
// 假设不作线程控制,则该两个被观察者会在同一个线程中工作,即发送事件存在先后顺序,而不是同时发送
<-- 使用zip变换操作符进行事件合并 -->
// 注:创建BiFunction对象传入的第3个参数 = 合并后数据的数据类型
Observable.zip(observable1, observable2, new BiFunction() {
@Override
public String apply(Integer integer, String string) throws Exception {
return integer + string;
}
}).subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String value) {
Log.d(TAG, "最终接收到的事件 = " + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError");
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
-
特别注意:
尽管被观察者2的事件D没有事件与其合并,但还是会继续发送
若在被观察者1 & 被观察者2的事件序列最后发送onComplete()事件,则被观察者2的事件D也不会发送
combineLatest()
- 作用
当两个Observables中的任何一个发送了数据后,将先发送了数据的Observables 的最新(最后)一个数据 与 另外一个Observable发送的每个数据结合,最终基于该函数的结果发送数据
与Zip()的区别:Zip() = 按个数合并,即1对1合并;CombineLatest() = 按时间合并,即在同一个时间点上合并
Observable.combineLatest(
Observable.just(1L, 2L, 3L), // 第1个发送数据事件的Observable
Observable.intervalRange(0, 3, 1, 1, TimeUnit.SECONDS), // 第2个发送数据事件的Observable:从0开始发送、共发送3个数据、第1次事件延迟发送时间 = 1s、间隔时间 = 1s
new BiFunction() {
@Override
public Long apply(Long o1, Long o2) throws Exception {
// o1 = 第1个Observable发送的最新(最后)1个数据
// o2 = 第2个Observable发送的每1个数据
Log.e(TAG, "合并的数据是: "+ o1 + " "+ o2);
return o1 + o2;
// 合并的逻辑 = 相加
// 即第1个Observable发送的最后1个数据 与 第2个Observable发送的每1个数据进行相加
}
}).subscribe(new Consumer() {
@Override
public void accept(Long s) throws Exception {
Log.e(TAG, "合并的结果是: "+s);
}
});
reduce()
- 作用
把被观察者需要发送的事件聚合成1个事件 & 发送
聚合的逻辑根据需求撰写,但本质都是前2个数据聚合,然后与后1个数据继续进行聚合,依次类推
Observable.just(1,2,3,4)
.reduce(new BiFunction() {
// 在该复写方法中复写聚合的逻辑
@Override
public Integer apply(@NonNull Integer s1, @NonNull Integer s2) throws Exception {
Log.e(TAG, "本次计算的数据是: "+s1 +" 乘 "+ s2);
return s1 * s2;
// 本次聚合的逻辑是:全部数据相乘起来
// 原理:第1次取前2个数据相乘,之后每次获取到的数据 = 返回的数据x原始下1个数据每
}
}).subscribe(new Consumer() {
@Override
public void accept(@NonNull Integer s) throws Exception {
Log.e(TAG, "最终计算的结果是: "+s);
}
});
collect()
- 作用
将被观察者Observable发送的数据事件收集到一个数据结构里
Observable.just(1, 2, 3 ,4, 5, 6)
.collect(
// 1. 创建数据结构(容器),用于收集被观察者发送的数据
new Callable>() {
@Override
public ArrayList call() throws Exception {
return new ArrayList<>();
}
// 2. 对发送的数据进行收集
}, new BiConsumer, Integer>() {
@Override
public void accept(ArrayList list, Integer integer)
throws Exception {
// 参数说明:list = 容器,integer = 后者数据
list.add(integer);
// 对发送的数据进行收集
}
}).subscribe(new Consumer>() {
@Override
public void accept(@NonNull ArrayList s) throws Exception {
Log.e(TAG, "本次发送的数据是: "+s);
}
});
3.3 发送事件前追加发送事件
startWith() / startWithArray()
- 作用
在一个被观察者发送事件前,追加发送一些数据 / 一个新的被观察者
<-- 在一个被观察者发送事件前,追加发送一些数据 -->
// 注:追加数据顺序 = 后调用先追加
Observable.just(4, 5, 6)
.startWith(0) // 追加单个数据 = startWith()
.startWithArray(1, 2, 3) // 追加多个数据 = startWithArray()
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
<-- 在一个被观察者发送事件前,追加发送被观察者 & 发送数据 -->
// 注:追加数据顺序 = 后调用先追加
Observable.just(4, 5, 6)
.startWith(Observable.just(1, 2, 3))
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
Log.d(TAG, "接收到了事件"+ value );
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
@Override
public void onComplete() {
Log.d(TAG, "对Complete事件作出响应");
}
});
3.4 统计发送事件数量
count()
- 作用
统计被观察者发送事件的数量
// 注:返回结果 = Long类型
Observable.just(1, 2, 3, 4)
.count()
.subscribe(new Consumer() {
@Override
public void accept(Long aLong) throws Exception {
Log.e(TAG, "发送的事件数量 = "+aLong);
}
});
4. 实际开发需求案例
组合 / 合并操作符的常见实际需求:
- 从缓存(磁盘、内存)中获取缓存数据
- 合并数据源
- 联合判断
4.1 从缓存(磁盘、内存)中获取缓存数据
-
需求场景
-
功能说明
// 该2变量用于模拟内存缓存 & 磁盘缓存中的数据
String memoryCache = null;
String diskCache = "从磁盘缓存中获取数据";
/*
* 设置第1个Observable:检查内存缓存是否有该数据的缓存
**/
Observable memory = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
// 先判断内存缓存有无数据
if (memoryCache != null) {
// 若有该数据,则发送
emitter.onNext(memoryCache);
} else {
// 若无该数据,则直接发送结束事件
emitter.onComplete();
}
}
});
/*
* 设置第2个Observable:检查磁盘缓存是否有该数据的缓存
**/
Observable disk = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
// 先判断磁盘缓存有无数据
if (diskCache != null) {
// 若有该数据,则发送
emitter.onNext(diskCache);
} else {
// 若无该数据,则直接发送结束事件
emitter.onComplete();
}
}
});
/*
* 设置第3个Observable:通过网络获取数据
**/
Observable network = Observable.just("从网络中获取数据");
// 此处仅作网络请求的模拟
/*
* 通过concat() 和 firstElement()操作符实现缓存功能
**/
// 1. 通过concat()合并memory、disk、network 3个被观察者的事件(即检查内存缓存、磁盘缓存 & 发送网络请求)
// 并将它们按顺序串联成队列
Observable.concat(memory, disk, network)
// 2. 通过firstElement(),从串联队列中取出并发送第1个有效事件(Next事件),即依次判断检查memory、disk、network
.firstElement()
// 即本例的逻辑为:
// a. firstElement()取出第1个事件 = memory,即先判断内存缓存中有无数据缓存;由于memoryCache = null,即内存缓存中无数据,所以发送结束事件(视为无效事件)
// b. firstElement()继续取出第2个事件 = disk,即判断磁盘缓存中有无数据缓存:由于diskCache ≠ null,即磁盘缓存中有数据,所以发送Next事件(有效事件)
// c. 即firstElement()已发出第1个有效事件(disk事件),所以停止判断。
// 3. 观察者订阅
.subscribe(new Consumer() {
@Override
public void accept( String s) throws Exception {
Log.d(TAG,"最终获取的数据来源 = "+ s);
}
});
4.2 合并数据源 & 同时展示数据
-
需求场景:
- 功能说明
同时向2个数据源获取数据 -> 合并数据 -> 统一展示到客户端 - 具体实现
此处采用Merge()
&Zip()
操作符进行讲解,其中:
Merge()例子 :实现较为简单的从(网络 + 本地)获取数据 & 统一展示
Zip()例子:结合Retrofit 与RxJava,实现较为复杂的合并2个网络请求向2个服务器获取数据 & 统一展示
4.2.1 采用 Merge()操作符
// 用于存放最终展示的数据
String result = "数据源来自 = " ;
/*
* 设置第1个Observable:通过网络获取数据
* 此处仅作网络请求的模拟
**/
Observable network = Observable.just("网络");
/*
* 设置第2个Observable:通过本地文件获取数据
* 此处仅作本地文件请求的模拟
**/
Observable file = Observable.just("本地文件");
/*
* 通过merge()合并事件 & 同时发送事件
**/
Observable.merge(network, file)
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
Log.d(TAG, "数据源有: "+ value );
result += value + "+";
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "对Error事件作出响应");
}
// 接收合并事件后,统一展示
@Override
public void onComplete() {
Log.d(TAG, "获取数据完成");
Log.d(TAG, result );
}
});
4.2.2 采用Zip()操作符
- 功能说明
从不同数据源(2个服务器)获取数据,即 合并网络请求的发送
统一显示结果
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Rxjava";
// 定义Observable接口类型的网络请求对象
Observable observable1;
Observable observable2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 步骤1:创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
.addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
.build();
// 步骤2:创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
// 步骤3:采用Observable<...>形式 对 2个网络请求 进行封装
observable1 = request.getCall().subscribeOn(Schedulers.io()); // 新开线程进行网络请求1
observable2 = request.getCall_2().subscribeOn(Schedulers.io());// 新开线程进行网络请求2
// 即2个网络请求异步 & 同时发送
// 步骤4:通过使用Zip()对两个网络请求进行合并再发送
Observable.zip(observable1, observable2,
new BiFunction() {
// 注:创建BiFunction对象传入的第3个参数 = 合并后数据的数据类型
@Override
public String apply(Translation1 translation1,
Translation2 translation2) throws Exception {
return translation1.show() + " & " +translation2.show();
}
}).observeOn(AndroidSchedulers.mainThread()) // 在主线程接收 & 处理数据
.subscribe(new Consumer() {
// 成功返回数据时调用
@Override
public void accept(String combine_infro) throws Exception {
// 结合显示2个网络请求的数据结果
Log.d(TAG, "最终接收到的数据是:" + combine_infro);
}
}, new Consumer() {
// 网络请求错误时调用
@Override
public void accept(Throwable throwable) throws Exception {
System.out.println("登录失败");
}
});
}
}
4.3 联合判断
- 需求场景
需要同时对多个事件进行联合判断
如,填写表单时,需要表单里所有信息(姓名、年龄、职业等)都被填写后,才允许点击 “提交” 按钮
- 具体实现
采用 RxJava 组合操作符中的combineLatest() 实现
/*
* 步骤1:设置控件变量 & 绑定
**/
EditText name,age,job;
Button list;
name = (EditText) findViewById(R.id.name);
age = (EditText) findViewById(R.id.age);
job = (EditText) findViewById(R.id.job);
list = (Button) findViewById(R.id.list);
/*
* 步骤2:为每个EditText设置被观察者,用于发送监听事件
* 说明:
* 1. 此处采用了RxBinding:RxTextView.textChanges(name) = 对对控件数据变更进行监听(功能类似TextWatcher),需要引入依赖:compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
* 2. 传入EditText控件,点击任1个EditText撰写时,都会发送数据事件 = Function3()的返回值(下面会详细说明)
* 3. 采用skip(1)原因:跳过 一开始EditText无任何输入时的空值
**/
Observable nameObservable = RxTextView.textChanges(name).skip(1);
Observable ageObservable = RxTextView.textChanges(age).skip(1);
Observable jobObservable = RxTextView.textChanges(job).skip(1);
/*
* 步骤3:通过combineLatest()合并事件 & 联合判断
**/
Observable.combineLatest(nameObservable,ageObservable,jobObservable,new Function3() {
@Override
public Boolean apply(@NonNull CharSequence charSequence, @NonNull CharSequence charSequence2, @NonNull CharSequence charSequence3) throws Exception {
/*
* 步骤4:规定表单信息输入不能为空
**/
// 1. 姓名信息
boolean isUserNameValid = !TextUtils.isEmpty(name.getText()) ;
// 除了设置为空,也可设置长度限制
// boolean isUserNameValid = !TextUtils.isEmpty(name.getText()) && (name.getText().toString().length() > 2 && name.getText().toString().length() < 9);
// 2. 年龄信息
boolean isUserAgeValid = !TextUtils.isEmpty(age.getText());
// 3. 职业信息
boolean isUserJobValid = !TextUtils.isEmpty(job.getText()) ;
/*
* 步骤5:返回信息 = 联合判断,即3个信息同时已填写,"提交按钮"才可点击
**/
return isUserNameValid && isUserAgeValid && isUserJobValid;
}
}).subscribe(new Consumer() {
@Override
public void accept(Boolean s) throws Exception {
/*
* 步骤6:返回结果 & 设置按钮可点击样式
**/
Log.e(TAG, "提交按钮是否可点击: "+s);
list.setEnabled(s);
}
});
原文链接:
Android RxJava操作符详解 系列:组合 / 合并操作符
Android RxJava 实战系列:从磁盘 / 内存缓存中 获取缓存数据
Android RxJava 实战讲解:合并数据源 & 同时展示数据
Android RxJava 实战系列:联合判断