RxJava安装与使用

RxJava: Reactive Extensions for the JVM

RxJava是Reactive Extensions的Java VM实现:一个通过使用可观察序列编写异步和基于事件的程序的库。

它扩展了观察者模式,以支持数据/事件序列,并添加了操作符,允许您以声明的方式将序列组合在一起,同时抽象出对底层线程、同步、线程安全和并发数据结构等问题的关注。

Version 3.x (Javadoc)

-单一依赖:Reactive-Streams

  • Java 8+ (Android desugar friendly)
  • java8lambda友好的API
    -修正了rxjava2的API错误和许多限制
    -旨在用相对较少的二进制不兼容更改来替代RxJava 2
    -对并发的来源(线程、池、事件循环、纤程、参与者等)不太在意。
    -异步或同步执行
    -参数化并发的虚拟时间和调度程序
    -测试和诊断支持通过测试调度程序,测试消费者和插件挂钩

了解更多关于RxJava的一般信息。

ℹ️ 请阅读 What’s different in 3.0以了解从2.x升级时的详细变化和迁移信息。

Version 2.x

2.x version 处于维护模式,直到2021年2月28日为止,只能通过bug修复得到支持。不接受或应用于2.x的新特性、行为更改或文档调整。

Version 1.x

1.x version 截止日期为 March 31, 2018.没有进一步的开发,支持,维护,PRs和更新将发生。 最新版本 1.3.8Javadoc 仍然可以访问。

开始

配置依赖

第一步是在你的项目里添加RxJava依赖,例如,作为一个Gradle编译依赖:

implementation "io.reactivex.rxjava3:rxjava:3.x.y"

(请将’ x ‘和’ y '用最新版本号代替:)

Hello World

第二部是去写一个 Hello World 程序:

package rxjava.examples;

import io.reactivex.rxjava3.core.*;

public class HelloWorld {
    public static void main(String[] args) {
        Flowable.just("Hello world").subscribe(System.out::println);
    }
}

注意,rxjava3组件现在在io.reactivex下运行。rxjava3和基类和接口位于io.reactivex.rxjava3.core之下。

Base classes

RxJava 3有几个基类,你可以发现操作:

一些术语

Upstream, downstream

RxJava中的数据流由一个源、零个或多个中间步骤组成,后面跟着一个数据消费者或组合子步骤(其中该步骤负责以某种方式消耗数据流):

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的文档中,emissionemissionitemeventsignaldatamessage被认为是同义词,表示沿着数据流运行的对象。

Backpressure

当数据流通过异步步骤运行时,每一步可能以不同的速度执行不同的事情。这类步骤通常会由于临时缓冲或需要跳过/删除数据而导致内存使用量增加,为了避免这些步骤过于庞大,应用了所谓的backpressure,这是一种流控制形式,其中步骤可以表示它们准备处理的项数。这允许在某些情况下限制数据流的内存使用,这些情况下,一个步骤通常无法知道上游将发送多少项给它。

在RxJava中,专用的Flowable类被指定为支持backpressure,而Observable类被指定为非backpressure操作(短序列、GUI交互等)。其他类型,SingleMaybeCompletable不支持反压,它们也不应该支持;总有地方可以暂时存放一件物品。

Assembly time

应用各种中间操作符进行数据流的准备工作发生在所谓的 assembly time:

Flowable<Integer> flow = Flowable.range(1, 5)
.map(v -> v * v)
.filter(v -> v % 3 == 0)
;

在这一点上,数据还没有流动,没有副作用发生。

Subscription time

当在内部建立处理步骤链的流程上调用subscribe() 时,这是一个临时状态:

flow.subscribe(System.out::println)

这是会触发 subscription side-effects (看 doOnSubscribe). 在这种状态下,一些源立即阻塞或开始发射项目。

Runtime

这是flow主动发送项目、错误或完成信号时的状态:


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);

实际上,这是在执行上面给出的示例的主体时。

简单的背景计算

RxJava的一个常见用例是在后台线程上运行一些计算、网络请求,并在UI线程上显示结果(或错误):

import io.reactivex.rxjava3.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

这种风格的链接方法称为连贯API,类似于builder模式。然而,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);

通常,你可以通过subscribeOn移动计算或阻塞IO到其他线程。一旦数据准备好了,你可以确保通过observeOn在前台或者GUI线程上处理它们。

Schedulers

RxJava操作符并不直接与‘Thread’或ExecutorService一起工作,而是与所谓的Scheduler一起工作,它将并发源抽象到统一的API后面。RxJava 3提供了几个可通过schedulers实用程序类访问的标准调度器。

  • Schedulers.computation(): 在后台使用固定数量的专用线程运行密集的计算工作。大多数异步操作符使用这个作为默认的Schedulers
  • Schedulers.io(): Run I/O-like or blocking operations on a dynamically changing set of threads.
  • Schedulers.single():对一组动态变化的线程运行类似I/ o的或阻塞的操作。
  • Schedulers.trampoline(): 通常为了测试的目的,在其中一个参与线程中按顺序和FIFO的方式运行工作。

These are available on all JVM platforms but some specific platforms, such as Android, have their own typical Schedulers defined: AndroidSchedulers.mainThread(), SwingScheduler.instance() or JavaFXSchedulers.gui().

此外,还有一个选项可以通过Schedulers.from(Executor)将现有的Executor(及其子类型,如ExecutorService)包装到Scheduler中。例如,可以使用它来拥有更大但仍然固定的线程池(不同于computation()io())。

最后出现的Thread.sleep(2000);并非偶然。在RxJava中,默认的调度器在守护进程线程上运行,这意味着一旦Java主线程退出,它们都将被停止,后台计算可能永远不会发生。在本例中,休眠一段时间可以让您查看控制台上流的输出,并有空闲时间。

Concurrency within a flow

RxJava中的流在本质上是顺序的,被分割为处理阶段,这些阶段可以相互并发地运行:

Flowable.range(1, 10)
  .observeOn(Schedulers.computation())
  .map(v -> v * v)
  .blockingSubscribe(System.out::println);

这个示例流在computationScheduler上将数字从1平方到10,并在main线程(更准确地说,是blockingSubscribe的调用线程)上消耗结果。但是,lambda v -> v * v并不在此流中并行运行;它在同一个计算线程上一个接一个地接收值1到10。

Parallel processing

Processing the numbers 1 to 10 in parallel is a bit more involved:

Flowable.range(1, 10)
  .flatMap(v ->
      Flowable.just(v)
        .subscribeOn(Schedulers.computation())
        .map(w -> w * w)
  )
  .blockingSubscribe(System.out::println);

实际上,RxJava中的并行性意味着运行独立的流,并将它们的结果合并回单个流。运算符flatMap首先将从1到10的每个数字映射成它自己的flow - able,运行它们并合并计算得到的方块。

但是,请注意,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 -> "Item " + inventoryItem.getName() + " has demand " + demand))
    .subscribe(System.out::println);

Continuations

有时,当一个项可用时,人们希望对它执行一些依赖的计算。这有时被称为continuations,根据应该发生的情况和所涉及的类型,可能需要使用各种操作符来完成。

Dependent

最典型的场景是给定一个值,调用另一个服务,等待并继续其结果:

service.apiCall()
.flatMap(value -> service.anotherApiCall(value))
.flatMap(next -> service.finalCall(next))

通常情况下,后面的序列会需要来自以前映射的值。这可以通过将外部的flatMap移动到之前的flatMap的内部来实现,例如:

service.apiCall()
.flatMap(value ->
    service.anotherApiCall(value)
    .flatMap(next -> service.finalCallBoth(value, next))
)

在这里,原始的“value”将在内部的“flatMap”中可用,这是由lambda变量capture提供的。

Non-dependent

在其他情况下,第一个源/数据流的结果是不相关的,人们希望继续使用准独立的另一个源。这里,“flatMap”也可以工作:

Observable continued = sourceObservable.flatMapSingle(ignored -> someSingleSource)
continued.map(v -> v.toString())
  .subscribe(System.out::println, Throwable::printStackTrace);

然而,在这种情况下,延续保持Observable,而不是更合适的Single。这是可以理解的,因为从flatMapSingle的角度来看,sourceObservable是一个多值源,因此映射也可能会产生多个值)。

通常有一种更有表现力(也更低开销)的方法,使用Completable作为中介和它的操作符,然后andThen使用其他东西:

sourceObservable
  .ignoreElements()           // returns Completable
  .andThen(someSingleSource)
  .map(v -> v.toString())

sourceObservablesomeSingleSource之间唯一的依赖是,前者应该正常完成,以便后者被消费。

Deferred-dependent

有时,前一个序列和新序列之间存在隐式的数据依赖关系,由于某种原因,这些依赖关系没有通过“常规通道”传递。人们倾向于将这种延续写如下:

AtomicInteger count = new AtomicInteger();

Observable.range(1, 10)
  .doOnNext(ignored -> count.incrementAndGet())
  .ignoreElements()
  .andThen(Single.just(count.get()))
  .subscribe(System.out::println);

不幸的是,这会输出’ 0 ',因为Single.just(count.get())是在assembly time时计算的,此时数据流还没有运行。我们需要一些东西来推迟这个Single源的评估直到 runtime当主源完成:

AtomicInteger count = new AtomicInteger();

Observable.range(1, 10)
  .doOnNext(ignored -> count.incrementAndGet())
  .ignoreElements()
  .andThen(Single.defer(() -> Single.just(count.get())))
  .subscribe(System.out::println);

或者

AtomicInteger count = new AtomicInteger();

Observable.range(1, 10)
  .doOnNext(ignored -> count.incrementAndGet())
  .ignoreElements()
  .andThen(Single.fromCallable(() -> count.get()))
  .subscribe(System.out::println);

Type conversions

有时,源或服务返回的类型与应该使用它的流不同。例如,在上面的库存示例中,getDemandAsync可以返回一个Single<DemandRecord>。如果代码示例保持不变,这将导致编译时错误(然而,通常带有关于缺少重载的误导性错误消息)。

在这种情况下,通常有两个选项来修复转换:1) 转换为所需的类型,或者2)查找并使用支持不同类型的特定操作符的重载。

转换为所需的类型

每个响应基类都提供可以执行此类转换(包括协议转换)的操作符,以匹配其他类型。下面的矩阵显示了可用的转换选项:

FlowableObservableSingleMaybeCompletable
FlowabletoObservablefirst, firstOrError, single, singleOrError, last, lastOrError1firstElement, singleElement, lastElementignoreElements
ObservabletoFlowable2first, firstOrError, single, singleOrError, last, lastOrError1firstElement, singleElement, lastElementignoreElements
SingletoFlowable3toObservabletoMaybeignoreElement
MaybetoFlowable3toObservabletoSingleignoreElement
CompletabletoFlowabletoObservabletoSingletoMaybe

1: 当将多值源转换为单值源时,应该决定在众多源值中考虑哪一个作为结果。

2: 将一个Observale转换为flowable需要一个额外的决定:如何处理潜在的不受约束的流
Observable的来源?有几种可用的策略(如缓冲,下降,保持最新),通过BackpressureStrategy参数或通过标准的flow操作符,如onBackpressureBufferonBackpressureDroponBackpressureLatest,这也是允许进一步定制反压行为。

3: 当只有(最多)一个源项时,反压力没有问题,因为它可以一直存储到下游准备消耗为止。

Using an overload with the desired type

许多经常使用的操作符具有可以处理其他类型的重载。它们通常以目标类型的后缀命名:

OperatorOverloads
flatMapflatMapSingle, flatMapMaybe, flatMapCompletable, flatMapIterable
concatMapconcatMapSingle, concatMapMaybe, concatMapCompletable, concatMapIterable
switchMapswitchMapSingle, switchMapMaybe, switchMapCompletable

这些操作符具有后缀,而不是简单地具有具有不同签名的相同名称的原因是类型擦除。Java不认为像operator(Function<T, Single<R>>)operator(Function<T,也许<R>>)这样的签名不同(不像c#),由于擦除,这两个operator最终会成为具有相同签名的重复方法。

运营商的命名约定

在编程中,命名是最困难的事情之一,因为名称应该不长、表达性强、易于捕获和记忆。不幸的是,目标语言(和预先存在的约定)在这方面可能不会提供太多帮助(不可用的关键字、类型擦去、类型歧义,等等)。

不能使用关键字

在原来的Rx中。NET中发出单个项并完成的操作符称为Return(T)。由于Java惯例是用小写字母开头一个方法名,这应该是return(T),这是Java中的关键字,因此不可用。因此,RxJava选择将这个操作符命名为just(T)。对于操作员“开关”也存在同样的限制,必须将其命名为switchOnNext。另一个例子是Catch它被命名为onErrorResumeNext

类型擦除

许多期望用户提供返回响应类型的函数的操作符不能重载,因为function <T, X>周围的类型擦除将这样的方法签名变成重复的。RxJava通过附加类型后缀来命名这样的操作符:

Flowable<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper)

Flowable<R> flatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper)
类型模棱两可

即使某些操作符没有类型擦除的问题,它们的签名可能出现歧义,特别是在使用Java 8和lambdas时。例如,有几个concatWith的重载,以各种其他反应基类型作为参数(为了在底层实现中提供方便和性能优势):

Flowable<T> concatWith(Publisher<? extends T> other);

Flowable<T> concatWith(SingleSource<? extends T> other);

PublisherSingleSource都以函数接口的形式出现(具有一个抽象方法的类型),并可能鼓励用户尝试提供lambda表达式:

someSource.concatWith(s -> Single.just(2))
.subscribe(System.out::println, Throwable::printStackTrace);

不幸的是,这种方法不起作用,示例根本没有打印2。实际上,自从2.1.10版本以来,它就不是了即使编译,因为至少4 concatWith重载存在,编译器发现上面的代码不明确。

在这种情况下,用户可能希望延迟一些计算直到someSource完成,因此正确明确操作符应该是defer:

someSource.concatWith(Single.defer(() -> Single.just(2)))
.subscribe(System.out::println, Throwable::printStackTrace);

有时,添加一个后缀是为了避免逻辑歧义,可能编译,但产生错误的类型流:

Flowable<T> merge(Publisher<? extends Publisher<? extends T>> sources);

Flowable<T> mergeArray(Publisher<? extends T>... sources);

当函数接口类型作为类型参数T涉及时,这也会变得含糊不清。

错误处理

数据流可能会失败,这时错误就会发送给使用者。但有时,多个源可能会失败,此时可以选择是等待所有源完成还是失败。为了表示这种可能性,许多操作符名称都以DelayError字为后缀(而其他操作符在重载时带有DelayErrordelayErrors布尔标志):

Flowable<T> concat(Publisher<? extends Publisher<? extends T>> sources);

Flowable<T> concatDelayError(Publisher<? extends Publisher<? extends T>> sources);

当然,各种后缀也可能一起出现:

Flowable<T> concatArrayEagerDelayError(Publisher<? extends T>... sources);
Base class vs base type

由于基类上有大量的静态和实例方法,因此可以认为基类很重。RxJava 3的设计受到了Reactive Streams规范的严重影响,因此,该库为每个反应类型提供了一个类和一个接口:

TypeClassInterfaceConsumer
0…N backpressuredFlowablePublisher1Subscriber
0…N unboundedObservableObservableSource2Observer
1 element or errorSingleSingleSourceSingleObserver
0…1 element or errorMaybeMaybeSourceMaybeObserver
0 element or errorCompletableCompletableSourceCompletableObserver

1org.reactivestreams.Publisher是外部反应流库的一部分。它是通过Reactive Streams specification管理的标准化机制与其他反应性库交互的主要类型。

2接口的命名约定是在半传统的类名后面附加Source。没有FlowableSource,因为Publisher是由反应流库提供的(而且子类型也不能帮助实现互操作)。然而,从反应流规范的角度来看,这些接口并不是标准的,目前仅是RxJava特定的。

R8 and ProGuard settings

默认情况下,RxJava本身不需要任何混淆器/R8设置,应该工作没有问题。不幸的是,自1.0.3版本以来的反应流依赖已经在它的JAR中嵌入了Java 9类文件,这可能会引起普通混淆器的警告:

Warning: org.reactivestreams.FlowAdapters$FlowPublisherFromReactive: can't find superclass or interface java.util.concurrent.Flow$Publisher
Warning: org.reactivestreams.FlowAdapters$FlowToReactiveProcessor: can't find superclass or interface java.util.concurrent.Flow$Processor
Warning: org.reactivestreams.FlowAdapters$FlowToReactiveSubscriber: can't find superclass or interface java.util.concurrent.Flow$Subscriber
Warning: org.reactivestreams.FlowAdapters$FlowToReactiveSubscription: can't find superclass or interface java.util.concurrent.Flow$Subscription
Warning: org.reactivestreams.FlowAdapters: can't find referenced class java.util.concurrent.Flow$Publisher

建议在应用程序的proguard-ruleset文件中设置以下-dontwarn条目:

-dontwarn java.util.concurrent.Flow*

对于R8, RxJava jar包括META-INF/proguard/rxjava3.pro与相同的无警告条款,应该自动适用。

Full Documentation

Binaries

Maven、Ivy、Gradle和其他文件的二进制文件和依赖信息可以在http://search.maven.org.

Example for Gradle:

implementation 'io.reactivex.rxjava3:rxjava:x.y.z'

and for Maven:

<dependency>
    <groupId>io.reactivex.rxjava3</groupId>
    <artifactId>rxjava</artifactId>
    <version>x.y.z</version>
</dependency>

and for Ivy:

<dependency org="io.reactivex.rxjava3" name="rxjava" rev="x.y.z" />

Snapshots

快照可以查看 rxjava3快照

repositories {
    maven { url 'https://oss.jfrog.org/libs-snapshot' }
}

dependencies {
    compile 'io.reactivex.rxjava3:rxjava:3.0.0-SNAPSHOT'
}

JavaDoc快照可以查看JavaDoc快照

Build

To build:

$ git clone git@github.com:ReactiveX/RxJava.git
$ cd RxJava/
$ ./gradlew build

关于构建的更多细节可以在wiki的Getting Started 页面上找到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值