RxJava 2.x与1.x变化及常用操作符
本文定位:学习笔记
学习过程记录,加深理解,提升文字组合表达能力。也希望能给学习RxJava的同学一些灵感
What's different in 2.0
本部分只对官方文档介绍部分做翻译,其他常用的少量翻译,需要详细了解查阅官方文档是最好的方式。
RxJava 2.0在Reactive-Streams规范的基础上完全重写。规范本身从RxJava 1.x发展而来,为反应系统和库提供了一个通用基线
RxJava 2.0 has been completely rewritten from scratch on top of the Reactive-Streams specification. The specification itself has evolved out of RxJava 1.x and provides a common baseline for reactive systems and libraries
由于Reactive-Streams具有不同的体系结构,因此它要求更改一些众所周知的RxJava类型。本wiki页面试图总结已更改的内容,并描述如何将1.x代码重写为2.x代码
Because Reactive-Streams has a different architecture, it mandates changes to some well known RxJava types. This wiki page attempts to summarize what has changed and describes how to rewrite 1.x code into 2.x code
有关如何为2.x编写运算符的技术细节,请访问Writing Operators wiki页面
For technical details on how to write operators for 2.x, please visit the Writing Operators wiki page.
Different Contents
- Maven address and base package
- Javadoc
- Nulls
- Observable and Flowable
- Single
- Completable
- Maybe
- Base reactive interfaces
- Subjects and Processors
- Other classes
- Functional interfaces
- Subscriber
- Subscription
- Backpressure
- Reactive-Streams compliance
- Runtime hooks
- Error handling
- Scheduler
- Entering the reactive world
- Leaving the reactive world
- Testing
- Operator differences
- Miscellaneous changes
Nulls处理方式
RxJava 2.x不再接受空值,下面将立即产生NullPointerException或作为下游信号:
RxJava 2.x no longer accepts null values and the following will yield NullPointerException immediately or as a signal to downstream:
Observable.just(null);
Single.just(null);
Observable.fromCallable(() -> null)
.subscribe(System.out::println, Throwable::printStackTrace);
Observable.just(1).map(v -> null)
.subscribe(System.out::println, Throwable::printStackTrace);
Observable and Flowable
关于在RxJava 0.x中引入背压的一个小遗憾是,Observable本身并没有单独的基础反应类,而是改造了。背压的主要问题是许多热点资源(如UI事件)不能合理地背压,并导致意外的MissingBackpressureException
(即初学者不期望它们)。
A small regret about introducing backpressure in RxJava 0.x is that instead of having a separate base reactive class, the Observable itself was retrofitted. The main issue with backpressure is that many hot sources, such as UI events, can't be reasonably backpressured and cause unexpected MissingBackpressureException (i.e., beginners don't expect them).
我们试图通过使io.reactivex.Observable无背压,并且新的io.reactivex.Flowable成为背压启用的基本反应类,来尝试在2.x中纠正这种情况。
We try to remedy this situation in 2.x by having io.reactivex.Observable non-backpressured and the new io.reactivex.Flowable be the backpressure-enabled base reactive class.
好消息是操作符仍然(大部分)保持不变。坏消息是,执行“导入”时应该小心,因为它可能会无意地选择了io.reactivex.Observable。
The good news is that operator names remain (mostly) the same. The bad news is that one should be careful when performing 'organize imports' as it may select the non-backpressured io.reactivex.Observable unintended.
Which type to use?
当设计数据流(作为RxJava的最终用户)或根据你的兼容库应该采用和返回哪种类型做决定时,可以考虑一些可帮助您避免出现问题的特性,避勉如MissingBackpressureException或OutOfMemoryError等问题。
When architecting dataflows (as an end-consumer of RxJava) or deciding upon what type your 2.x compatible library should take and return, you can consider a few factors that should help you avoid problems down the line such as MissingBackpressureException or OutOfMemoryError.
When to use Observable
- 数据量少于1000,事件很少,应用不可能OOM
- 您处理GUI事件,例如鼠标移动或触摸事件:这些事件很少会被合理地反压,而且并不常见。您可能可以使用Observable处理1000 Hz或更低的元素频率,但无论如何都要考虑使用采样/去抖动的方式。
- 您的流程基本上是同步的,但您的平台不支持Java Streams,或者您错过了它的功能。通常使用Observable的开销比Flowable低。 (您也可以考虑为支持Java 6+的Iterable流优化的IxJava)。
英文原文:
- You have a flow of no more than 1000 elements at its longest: i.e., you have so few elements over time that there is practically no chance for OOME in your application.
- You deal with GUI events such as mouse moves or touch events: these can rarely be backpressured reasonably and aren't that frequent. You may be able to handle an element frequency of 1000 Hz or less with Observable but consider using sampling/debouncing anyway.
- Your flow is essentially synchronous but your platform doesn't support Java Streams or you miss features from it. Using Observable has lower overhead in general than Flowable. (You could also consider IxJava which is optimized for Iterable flows supporting Java 6+).
When to use Flowable
- Dealing with 10k+ of elements that are generated in some fashion somewhere and thus the chain can tell the source to limit the amount it generates.
- Reading (parsing) files from disk is inherently blocking and pull-based which works well with backpressure as you control, for example, how many lines you read from this for a specified request amount).
- Reading from a database through JDBC is also blocking and pull-based and is controlled by you by calling ResultSet.next() for likely each downstream request.
- Network (Streaming) IO where either the network helps or the protocol used supports requesting some logical amount.
- Many blocking and/or pull-based data sources which may eventually get a non-blocking reactive API/driver in the future.
常用操作符
代码示例部分,部分基于1.x,部分基于2.x,但是二者无太大差别
map
事件对象的直接变换
Observable.just("images/logo.png") // 输入类型 String
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) { // 参数类型 String
return getBitmapFromPath(filePath); // 返回类型 Bitmap
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) { // 参数类型 Bitmap
showBitmap(bitmap);
}
});
flatMap
latMap 操作符可以将一个发射数据的 Observable 变换为多个 Observables ,然后将它们发射的数据合并后放到一个单独的 Observable
首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onNext(String name) {
Log.d(tag, name);
}
...
};
Observable.from(students)
.map(new Func1<Student, String>() {
@Override
public String call(Student student) {
return student.getName();
}
})
.subscribe(subscriber);
那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个 Course 呢?这个时候,就需要用 flatMap() 了:
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
@Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
@Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。 flatMap() 的原理是这样的:
-
使用传入的事件对象创建一个 Observable 对象;
-
并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
-
每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。
这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。
concat
concat 可以做到不交错的发射两个甚至多个 Observable 的发射事件,并且只有前一个 Observable 终止(onComplete) 后才会订阅下一个 Observable
利用 concat 的必须调用 onComplete 后才能订阅下一个 Observable 的特性,我们就可以先读取缓存数据,倘若获取到的缓存数据不是我们想要的,再调用 onComplete() 以执行获取网络数据的 Observable,如果缓存数据能应我们所需,则直接调用 onNext(),防止过度的网络请求,浪费用户的流量
Observable<FoodList> cache = Observable.create(new ObservableOnSubscribe<FoodList>() {
@Override
public void subscribe(@NonNull ObservableEmitter<FoodList> e) throws Exception {
Log.e(TAG, "create当前线程:"+Thread.currentThread().getName() );
FoodList data = CacheManager.getInstance().getFoodListData();
// 在操作符 concat 中,只有调用 onComplete 之后才会执行下一个 Observable
if (data != null){ // 如果缓存数据不为空,则直接读取缓存数据,而不读取网络数据
isFromNet = false;
Log.e(TAG, "\nsubscribe: 读取缓存数据:" );
runOnUiThread(new Runnable() {
@Override
public void run() {
mRxOperatorsText.append("\nsubscribe: 读取缓存数据:\n");
}
});
e.onNext(data);
}else {
isFromNet = true;
runOnUiThread(new Runnable() {
@Override
public void run() {
mRxOperatorsText.append("\nsubscribe: 读取网络数据:\n");
}
});
Log.e(TAG, "\nsubscribe: 读取网络数据:" );
e.onComplete();
}
}
});
Observable<FoodList> network = Rx2AndroidNetworking.get("http://www.tngou.net/api/food/list")
.addQueryParameter("rows",10+"")
.build()
.getObjectObservable(FoodList.class);
// 两个 Observable 的泛型应当保持一致
Observable.concat(cache,network)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<FoodList>() {
@Override
public void accept(@NonNull FoodList tngouBeen) throws Exception {
Log.e(TAG, "subscribe 成功:"+Thread.currentThread().getName() );
if (isFromNet){
mRxOperatorsText.append("accept : 网络获取数据设置缓存: \n");
Log.e(TAG, "accept : 网络获取数据设置缓存: \n"+tngouBeen.toString() );
CacheManager.getInstance().setFoodListData(tngouBeen);
}
mRxOperatorsText.append("accept: 读取数据成功:" + tngouBeen.toString()+"\n");
Log.e(TAG, "accept: 读取数据成功:" + tngouBeen.toString());
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "subscribe 失败:"+Thread.currentThread().getName() );
Log.e(TAG, "accept: 读取数据失败:"+throwable.getMessage() );
mRxOperatorsText.append("accept: 读取数据失败:"+throwable.getMessage()+"\n");
}
});
zip
zip 操作符可以将多个 Observable 的数据结合为一个数据源再发射出去
Observable<MobileAddress> observable1 = Rx2AndroidNetworking.get("http://api.avatardata.cn/MobilePlace/LookUp?key=ec47b85086be4dc8b5d941f5abd37a4e&mobileNumber=13021671512")
.build()
.getObjectObservable(MobileAddress.class);
Observable<CategoryResult> observable2 = Network.getGankApi()
.getCategoryData("Android",1,1);
Observable.zip(observable1, observable2, new BiFunction<MobileAddress, CategoryResult, String>() {
@Override
public String apply(@NonNull MobileAddress mobileAddress, @NonNull CategoryResult categoryResult) throws Exception {
return "合并后的数据为:手机归属地:"+mobileAddress.getResult().getMobilearea()+"人名:"+categoryResult.results.get(0).who;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(@NonNull String s) throws Exception {
Log.e(TAG, "accept: 成功:" + s+"\n");
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "accept: 失败:" + throwable+"\n");
}
});
interval
创建一个按照给定的时间间隔发射从0开始的整数序列的Observable<Long>,内部通过OnSubscribeTimerPeriodically工作。
Observable.interval(1, TimeUnit.SECONDS)
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
//每隔1秒发送数据项,从0开始计数
//0,1,2,3....
}
});
参考
- https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#nulls
- http://gank.io/post/560e15be2dca930e00da1083#toc_8
- https://blog.csdn.net/maplejaw_/article/details/52396175