参考并翻译:https://github.com/ReactiveX/RxJava
RxJava:Reactive Extensions 在JVM上的实现
RxJava 是Reactive Extensions(使用可观察的序列来整合异步与事件程序的库)在Java虚拟机上的实现。
它基于观察者模式来支持数据/事件的序列化以及添加操作者来组合各种序列并抽象化各种事物例如低等级线程,同步,线程安全,当前数据结构。
版本2.x
- 单一依赖:Reactive-Streams
- 继续支持Java 6+ 和Android 2.3+
- 根据1.x的使用对设计做出了一定的调整同时对Reactive-Streams-Commons工程的研究对2.x做了一定的升级
- Java 8 对 lambda友好支持的API
- 灵活的并发源((threads, pools, event loops, fibers, actors等等)
- 异步或同步执行
- 支持参数化并发的虚拟时间和调度
版本2.x和1.x会同时存在几年,有不同的组ID(io.reactivex.rxjava2 vs io.reactivex)和命名空间(io.reactivex vs rx).
要看版本1.x与2.x之间的差异,可以看维基文章:What’s different in 2.0.学习更多关于RxJava一般就是Wiki Home.
版本1.x
版本1.x已经于2018.3.31全部结束。不再有更多的进展,支持,维护,公开关系或是更新。最后一份javadoc版本是1.3.8,可以继续查看。
开始
设置依赖
第一步是将RxJava 2加入你的工程,例如用Gradle进行依赖编译:
compile "io.reactivex.rxjava2:rxjava:2.x.y"
(请将x和y替换成最新的版本号,如2.1.14)
Hello World
第二步是写一个Hello World程序:
package rxjava.examples;
import io.reactivex.*;
public class HelloWorld {
public static void main(String[] args) {
Flowable.just("Hello world").subscribe(System.out::println);
}
}
如果你的平台还不支持Java8 lambdas,就创建手动一个经典的内部类:
import io.reactivex.functions.Consumer;
Flowable.just("Hello world")
.subscribe(new Consumer<String>() {
@Override public void accept(String s) {
System.out.println(s);
}
});
基本的类
RxJava 2 的一些基础类有一些特征可用从以下文档中找到使用指南:
- io.reactivex.Flowable: 0..N flows, supporting Reactive-Streams and backpressure
- io.reactivex.Observable: 0..N flows, no backpressure,
- io.reactivex.Single: a flow of exactly 1 item or an error,
- io.reactivex.Completable: a flow without items but only a completion or error signal,
- io.reactivex.Maybe: a flow with no items, exactly one item or an error.
一些术语
Upstream, downstream
RxJava的数据流是由一个来源,0到多个中间步骤以及一个数据消费和组合过程(消费数据流可以通过某种方式进行响应):
source.operator1().operator2().operator3().subscribe(consumer);
source.flatMap(value -> source.operator1().operator2().operator3());
这里,我们可以想象一下,我们自己是在operator2,左边的流叫upstream,右边的方向,顺着用户或者消费者方向的是downstream,当每个元素都分开写成一行时,这个过程会显得更加清晰:
source
.operator1()
.operator2()
.operator3()
.subscribe(consumer)
Objects in motion
在RxJava文档中,emission, emits, item, event, signal, data and message都可以认为是同个概念,代表着对象顺着数据流方向移动。
Backpressure
当数据流是按异步流动时,每一步都可以按不同的速度进行不同的处理。为了避免某些步骤,在处理时会因为临时数据或者中间数据导致的内存不断增加,于是就有了Backpressure。Backpressure是流控制的一种格式,每个步骤都可以按这个格式表达他们可以处理的数据量。某个步骤通常是无法知道Upstream到底会有多少数据量传过来,Backpressure就很好地控制了内存的使用了。
Assembly time
Assembly time是指在这个时间内通过各种中间步骤的操作数据流才能准备。
Flowable<Integer> flow = Flowable.range(1, 5)
.map(v -> v* v)
.filter(v -> v % 3 == 0);
从这个角度说,数据实际上是还没开始流动,各种反应还没起作用。
Runtime
指的是流从发出到出错或者到完成的这个过程的状态。
Observable.create(emitter -> {
while (!emitter.isDisposed()) {
long time = System.currentTimeMillis();
emitter.onNext(time);
if (time % 2 != 0) {
emitter.onError(new IllegalStateException("Odd millisecond!"));
break;
}
}
})
.subscribe(System.out::println, Throwable::printStackTrace);
实际上,也就是上面例子中正文执行的时间
Simple background computation
RxJava其中一个常规用途是在后台线程中计算处理、网络请求,在前台UI界面显示结果或者错误。
import io.reactivex.schedulers.Schedulers;
Flowable.fromCallable(() -> {
Thread.sleep(1000); // imitate expensive computation
return "Done";
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.single())
.subscribe(System.out::println, Throwable::printStackTrace);
Thread.sleep(2000); // <--- wait for the flow to finish
这种一连串方法调用的形式叫做fluent API,类似于生成器模式,只是RxJava的反应类型是不可变的,每个方法返回的类型是叠加行为后的Flowable类型。以下例子可以被写成流模式:
Flowable<String> source = Flowable.fromCallable(() -> {
Thread.sleep(1000); // imitate expensive computation
return "Done";
});
Flowable<String> runBackground = source.subscribeOn(Schedulers.io());
Flowable<String> showForeground = runBackground.observeOn(Schedulers.single());
showForeground.subscribe(System.out::println, Throwable::printStackTrace);
Thread.sleep(2000);
通常可以通过订阅将计算移到其他线程或者停止IO到其他线程。一旦数据准备好,就可以通过观察者保证他们在前台或者GUI线程处理。
Schedulers
RxJava的操作并不是直接与线程或者是执行服务打交道而是与一个统一API下的抽象化并发源打交道,叫做Schedulers。RxJava 2通过Schedulers实用类定义了一些标准的调度器。
- Schedulers.computation(): 以一定数量的专用线程运行计算强任务。大部分一部处理器把它们作为默认的调度器。
- Schedulers.io():以 一组动态变化的线程集合运行类I/O或者阻塞操作。
- Schedulers.single(): 在单一线程中以顺序和FIFO方式运行。
- Schedulers.trampoline(): 在一个可参与的线程中以顺序和FIFO的方式运行,通常用于测试。
这些在所有的JVM平台上都可以使用,除了一些特殊的平台,像Android有他们自己定义的典型的Schedulers:
AndroidSchedulers.mainThread(), SwingScheduler.instance() ,JavaFXSchedulers.gui().
另外,可以通过调度器格式(Executor)来包装一个存在的Executor(以及它的子类型例如ExecutorService)。例如它可以用于更大的固定线程池(不像computation() and io() ).
Thread.sleep(2000);放在最后并不是偶然的。在RxJava中默认的Schedulers在守护进程中运行。这也就意味着一旦Java主线程不存在,他们都会停止并且后台计算也就不会发生了。在这个例子中睡眠一段时间可以让你在控制台中看到流随着时间的推移怎样输出。
Concurrency within a flow
在RxJava中的流本质上是顺序的,可以分成多个处理阶段,各个阶段之间可能是同时运行的。
Flowable.range(1, 10)
.observeOn(Schedulers.computation())
.map(v -> v * v)
.blockingSubscribe(System.out::println);
这个例子的流在计算调度器中计算1-10的平方并且在主线程中消耗结果(更精确地说是blockingSubscribe的调用线程)。然而lambda表达式v->v*v对流来说并不是并行运行的,它在同一个计算线程中一个接一个地收到数值1-10。
Parallel processing
并行处理1-10有点难懂:
Flowable.range(1, 10)
.flatMap(v ->
Flowable.just(v)
.subscribeOn(Schedulers.computation())
.map(w -> w * w)
)
.blockingSubscribe(System.out::println);
实际上,在RxJava中并行指的是运行多个独立的流并将他们的结果合并到一个单独的流中。
flatMap通过将1-10的数字划分到自己的独立的Flowable中,并运行最终将计算的平方值合并。
注意,flatMap不保证顺序并且内部流的结果是错乱结束的。以下是可替换的操作:
- concatMap 在一个时间内列表并运行一个内部流
concatMapEager 立即运行所有的内部流而且输出流将会按着内部流创建的顺序进行。
以下有个测试操作Flowable.parallel(),以及类型ParallelFlowable帮助达到相同的并行处理模式:Flowable.range(1, 10)
.parallel()
.runOn(Schedulers.computation())
.map(v -> v * v)
.sequential()
.blockingSubscribe(System.out::println);
Dependent sub-flows
flatMap 是一个有用的操作在很多情况下都有用到。例如,给定一个返回Flowable的服务,我们希望用第一个服务发出的数值来调用另一个服务:
Flowable<Inventory> inventorySource = warehouse.getInventoryAsync();
inventorySource.flatMap(inventoryItem ->
erp.getDemandAsync(inventoryItem.getId())
.map(demand
-> System.out.println("Item " + inventoryItem.getName() + " has demand " + demand));
)
.subscribe();