有些翻譯仍不準(zhǔn)確,會(huì)持續(xù)改進(jìn)。
術(shù)語
上游,下游
RxJava中的數(shù)據(jù)流包括一個(gè)數(shù)據(jù)源、0個(gè)或多個(gè)中間步驟、一個(gè)數(shù)據(jù)消費(fèi)者或組合子步驟(其中的步驟負(fù)責(zé)以某種方式使用數(shù)據(jù)流):
source.operator1().operator2().operator3().subscribe(consumer);
source.flatMap(value -> source.operator1().operator2().operator3());
如果我們想象自己站在operator2上,向左看向source,叫做上游;向右看向subscriber/consumer,叫做下游。當(dāng)像下面這樣每個(gè)元素單獨(dú)寫一行時(shí)看的更加明顯:
source
.operator1()
.operator2()
.operator3()
.subscribe(consumer)
運(yùn)動(dòng)的對(duì)象
在RxJava的文檔中, emission, emits, item, event, signal, data and message都是近義詞,都表示沿著數(shù)據(jù)流移動(dòng)的對(duì)象。
背壓
當(dāng)數(shù)據(jù)流通過異步步驟運(yùn)行時(shí),每個(gè)步驟可能以不同的速度執(zhí)行不同的事情。為了避免那些由于臨時(shí)緩沖或需要跳過/刪除數(shù)據(jù)而導(dǎo)致內(nèi)存使用量增加的步驟被淹沒,因此應(yīng)用了所謂的背壓,這是一種流控制形式,從而步驟可以表示準(zhǔn)備處理多少項(xiàng)數(shù)據(jù)。使用背壓允許當(dāng)前步驟在通常無法知道上游將發(fā)送多少項(xiàng)數(shù)據(jù)的情況下限制數(shù)據(jù)流的內(nèi)存使用。
在RxJava中,Flowable
類被設(shè)計(jì)成支持背壓,Observable
類專用于非背壓操作。Single, Maybe and Completable
也不支持背壓。
裝配時(shí)間Assembly time
通過應(yīng)用各式各樣的中間操作符來準(zhǔn)備數(shù)據(jù)流,發(fā)生在裝配時(shí)間。
Flowable<Integer> flow = Flowable.range(1, 5)
.map(v -> v * v)
.filter(v -> v % 3 == 0)
;
在當(dāng)前點(diǎn)上,數(shù)據(jù)還沒有流動(dòng),也沒有發(fā)生副作用。
訂閱時(shí)間 Subscription time
這是在內(nèi)部建立處理步驟鏈的流上調(diào)用subscribe()時(shí)的臨時(shí)狀態(tài):
flow.subscribe(System.out::println)
這時(shí)會(huì)觸發(fā)訂閱副作用。有些源在這種狀態(tài)下會(huì)立即阻塞或開始發(fā)送數(shù)據(jù)項(xiàng)。
運(yùn)行時(shí) Runtime
這是數(shù)據(jù)流主動(dòng)發(fā)出數(shù)據(jù)項(xiàng)、錯(cuò)誤或完成信號(hào)時(shí)的狀態(tài):
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);
實(shí)際上,這是上面給定示例的主體執(zhí)行的時(shí)候。
簡單的后臺(tái)計(jì)算
RxJava的一個(gè)常見用例是在后臺(tái)線程上運(yùn)行一些計(jì)算、網(wǎng)絡(luò)請(qǐng)求,并在UI線程上顯示結(jié)果(或錯(cuò)誤)
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
這種鏈?zhǔn)秸{(diào)用方法的形式稱為流式API,類似于builder模式。然而,RxJava的響應(yīng)類型是不可變的;每個(gè)方法調(diào)用都返回一個(gè)添加了行為的新的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
將計(jì)算或阻塞IO移動(dòng)到其他線程。一旦數(shù)據(jù)準(zhǔn)備好,您就可以通過observeOn
確保它們?cè)谇芭_(tái)或GUI線程上得到處理。
調(diào)度者Schedulers
RxJava操作符不直接使用Thread或ExecutorServices,而是使用所謂的調(diào)度器Scheduler,這些調(diào)度器將并發(fā)源抽象到統(tǒng)一API后面。RxJava 2提供了幾個(gè)可通過scheduler類訪問的標(biāo)準(zhǔn)調(diào)度器。
- Schedulers.computation(): 在后臺(tái)的固定數(shù)量的專用線程上運(yùn)行計(jì)算密集型工作。大多數(shù)異步操作符將此作為其默認(rèn)調(diào)度程序。
- Schedulers.io(): 在一組動(dòng)態(tài)變化的線程上運(yùn)行類似I/ o或阻塞操作。
- Schedulers.single(): 以順序和FIFO方式在單個(gè)線程上運(yùn)行工作。
- Schedulers.trampoline(): 在參與的線程中以順序和FIFO方式運(yùn)行工作,通常用于測(cè)試目的。
這些在所有JVM平臺(tái)上都可用,但是在某些特定的平臺(tái)上,例如android,有它們特有的調(diào)度器:AndroidSchedulers.mainThread(), SwingScheduler.instance() or JavaFXSchedulers.gui()
.
此外,還可以通過Scheduler .from(Executor)
將現(xiàn)有的Executor
(及其子類型,如ExecutorService
)包裝到調(diào)度器Schedulers中。例如,可以使用它來擁有更大但仍然固定的線程池(不同于分別使用compute()和io())。
上面例子結(jié)尾處的Thread.sleep(2000);
是有意為之。在RxJava中,默認(rèn)調(diào)度程序在守護(hù)線程上運(yùn)行,這意味著一旦Java主線程退出,它們就會(huì)全部停止,后臺(tái)計(jì)算可能永遠(yuǎn)不會(huì)發(fā)生。在這個(gè)示例場(chǎng)景中,休眠一段時(shí)間可以讓您看到控制臺(tái)上數(shù)據(jù)流流的輸出。
流中的并發(fā)
RxJava中的流本質(zhì)上是順序的,它們被劃分為可以彼此并發(fā)運(yùn)行的處理階段
Flowable.range(1, 10)
.observeOn(Schedulers.computation())
.map(v -> v * v)
.blockingSubscribe(System.out::println);
這個(gè)示例流將computation調(diào)度器上的數(shù)字從1平方到10,并在主線程(更準(zhǔn)確地說,是blockingSubscribe的調(diào)用線程)上處理結(jié)果。然而lambda表達(dá)式v -> v * v
并不是并行運(yùn)行的。它在同一個(gè)計(jì)算線程上依次接收1到10的值。
并行處理
并行處理數(shù)字1到10稍微復(fù)雜一些:
Flowable.range(1, 10)
.flatMap(v ->
Flowable.just(v)
.subscribeOn(Schedulers.computation())
.map(w -> w * w)
)
.blockingSubscribe(System.out::println);
實(shí)際上,RxJava中的并行性意味著運(yùn)行獨(dú)立的流并將它們的結(jié)果合并回單個(gè)流。操作符flatMap首先將1到10的每個(gè)數(shù)字映射到它自己的Flowable中,運(yùn)行它們并合并計(jì)算的平方。
但是,請(qǐng)注意,flatMap不保證任何順序,來自內(nèi)部流的最終結(jié)果可能是交錯(cuò)的。有其他的操作符可供選擇:
-
concatMap
它每次映射并運(yùn)行一個(gè)內(nèi)部流 -
concatMapEager
它“同時(shí)”運(yùn)行所有內(nèi)部流,但是輸出流將按照這些內(nèi)部流創(chuàng)建的順序輸出。
或者,Flowable.parallel()
操作符和ParallelFlowable
類型可以幫助實(shí)現(xiàn)相同的并行處理模式:
Flowable.range(1, 10)
.parallel()
.runOn(Schedulers.computation())
.map(v -> v * v)
.sequential()
.blockingSubscribe(System.out::println);
依賴子流
flatMap是一個(gè)強(qiáng)大的操作符,在很多情況下都有幫助。例如,給定一個(gè)返回Flowable的服務(wù),我們希望使用第一個(gè)服務(wù)發(fā)出的值調(diào)用另一個(gè)服務(wù):
Flowable<Inventory> inventorySource = warehouse.getInventoryAsync();
inventorySource.flatMap(inventoryItem ->
erp.getDemandAsync(inventoryItem.getId())
.map(demand
-> System.out.println("Item " + inventoryItem.getName() + " has demand " + demand));
)
.subscribe();
延續(xù)Continuations
有時(shí),當(dāng)一個(gè)項(xiàng)變得可用時(shí),人們希望依賴它執(zhí)行一些計(jì)算。這有時(shí)稱為延續(xù),根據(jù)將要發(fā)生的情況和涉及的類型,可能需要不同的操作符來實(shí)現(xiàn)。
依賴
最典型的場(chǎng)景是給定一個(gè)值,調(diào)用另一個(gè)服務(wù),等待并使用其結(jié)果繼續(xù):
service.apiCall()
.flatMap(value -> service.anotherApiCall(value))
.flatMap(next -> service.finalCall(next))
通常情況下,后面的序列也需要來自前面映射的值。這可以通過將外部flatMap
移動(dòng)到上一個(gè)flatMap
的內(nèi)部來實(shí)現(xiàn),例如:
service.apiCall()
.flatMap(value ->
service.anotherApiCall(value)
.flatMap(next -> service.finalCallBoth(value, next))
)
在這里,由于lambda變量捕獲,原始value
將在內(nèi)部flatMap
中可用。
非依賴
在其他場(chǎng)景中,第一個(gè)源/數(shù)據(jù)流的結(jié)果是不相關(guān)的,我們希望使用獨(dú)立的另一個(gè)源繼續(xù)。flatMap
也可以勝任:
Observable continued = sourceObservable.flatMapSingle(ignored -> someSingleSource)
continued.map(v -> v.toString())
.subscribe(System.out::println, Throwable::printStackTrace);
然而,在這種情況下,延續(xù)保持Observable ,而不是可能更合適的Single.(這是可以理解的,因?yàn)閺膄latMapSingle的角度來看,sourceObservable是一個(gè)多值源,因此映射也可能導(dǎo)致多個(gè)值)
雖然通常有一種方法更有表現(xiàn)力(也更低的開銷),即使用Completable作為中介,然后使用它的操作符andThen來繼續(xù):
sourceObservable
.ignoreElements() // returns Completable
.andThen(someSingleSource)
.map(v -> v.toString())
sourceObservable和someSingleSource之間唯一的依賴關(guān)系是前者應(yīng)該正常完成,以便使用后者。
延遲依賴
有時(shí),前一個(gè)序列和新序列之間存在隱式的數(shù)據(jù)依賴關(guān)系,由于某些原因,該依賴關(guān)系沒有通過“常規(guī)通道”流動(dòng)。有人會(huì)傾向于這樣寫延續(xù):
AtomicInteger count = new AtomicInteger();
Observable.range(1, 10)
.doOnNext(ignored -> count.incrementAndGet())
.ignoreElements()
.andThen(Single.just(count.get()))
.subscribe(System.out::println);
不幸的是,這輸出0,因?yàn)?code>Single.just(count.get())是在數(shù)據(jù)流尚未運(yùn)行的assembly time期間計(jì)算的。我們需要一些東西,來推遲Single來源的計(jì)算,直到主要來源完成的運(yùn)行時(shí):
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);
類型轉(zhuǎn)換
有時(shí),源或服務(wù)返回的類型與應(yīng)該使用它的流不同。 例如,在上面的inventory例子中,getDemandAsync
可能返回一個(gè)Single<DemandRecord>
. 如果代碼示例保持不變,則會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤(但是,通常會(huì)出現(xiàn)關(guān)于缺少重載的誤導(dǎo)性錯(cuò)誤消息)。
在這種情況下,通常有兩種方式來修復(fù)轉(zhuǎn)換: 1) 轉(zhuǎn)換為所需類型 2) 查找并使用支持不同類型的特定操作符的重載。
轉(zhuǎn)換為所需類型
每個(gè)響應(yīng)基類都具有可以執(zhí)行此類轉(zhuǎn)換(包括協(xié)議轉(zhuǎn)換)的操作符,以匹配其他類型。 下面的矩陣顯示了可用的轉(zhuǎn)換選項(xiàng):
Flowable | Observable | Single | Maybe | Completable | |
---|---|---|---|---|---|
Flowable | toObservable |
first , firstOrError , single , singleOrError , last , lastOrError 1
|
firstElement , singleElement , lastElement
|
ignoreElements |
|
Observable |
toFlowable 2
|
first , firstOrError , single , singleOrError , last , lastOrError 1
|
firstElement , singleElement , lastElement
|
ignoreElements |
|
Single |
toFlowable 3
|
toObservable |
toMaybe |
ignoreElement |
|
Maybe |
toFlowable 3
|
toObservable |
toSingle |
ignoreElement |
|
Completable | toFlowable |
toObservable |
toSingle |
toMaybe |
1: 當(dāng)將一個(gè)多值源轉(zhuǎn)換為一個(gè)單值源時(shí),你應(yīng)該決定使用多個(gè)源中的哪一個(gè)值作為結(jié)果。
2: 把一個(gè)Observable
變成Flowable
需要一個(gè)額外的決定: 如何處理Observable
源中潛在的無約束流? 通過BackpressureStrategy
參數(shù)或標(biāo)準(zhǔn)的Flowable
操作符(如onBackpressureBuffer
、onBackpressureDrop
、onbackpressurerelatest
,這些操作符還允許進(jìn)一步定制反壓力行為。)可以使用幾種策略(如緩沖、刪除、保持最新狀態(tài))來處理。
3: 當(dāng)只有(最多)一個(gè)源數(shù)據(jù)項(xiàng)時(shí),背壓沒有問題,因?yàn)樗梢砸恢贝鎯?chǔ)到下游準(zhǔn)備使用為止。
使用具有所需類型的重載
許多常用的操作符都有可以處理其他類型的重載。它們通常以目標(biāo)類型的后綴命名:
Operator | Overloads |
---|---|
flatMap |
flatMapSingle , flatMapMaybe , flatMapCompletable , flatMapIterable
|
concatMap |
concatMapSingle , concatMapMaybe , concatMapCompletable , concatMapIterable
|
switchMap |
switchMapSingle , switchMapMaybe , switchMapCompletable
|
這些操作符具有后綴而不是簡單地使用具有不同簽名的相同名稱的原因是類型擦除。 Java認(rèn)為像operator(Function<T, Single<R>>)
和 operator(Function<T, Maybe<R>>)
這樣的簽名是相同的(與c#不同),并且由于擦除的原因,這兩operator
最終會(huì)成為具有相同簽名的重復(fù)方法。
操作符命名約定
命名在編程中是最困難的事情之一,因?yàn)槊蟛婚L、具有表現(xiàn)力、容易捕捉和容易記憶。 不幸的是,目標(biāo)語言(和已經(jīng)存在的約定)在這方面可能不會(huì)提供太多的幫助(不可用的關(guān)鍵字、類型擦除、類型歧義等等)。
不可用關(guān)鍵字
在原始的Rx.NET
中,發(fā)出單個(gè)項(xiàng)然后完成的運(yùn)算符稱為 Return(T)
.由于Java約定是以小寫字母開頭的方法名,因此它應(yīng)該是return(T)
,但這是Java中的關(guān)鍵字,因此不可用。 因此,RxJava選擇將這個(gè)操作符命名為 just(T)
. 操作符Switch
也存在同樣的限制,必須將其命名為 switchOnNext
.另一個(gè)例子是Catch
,它被命名為 onErrorResumeNext
.
類型消除
許多期望用戶提供返回響應(yīng)類型的函數(shù)的操作符無法重載,因?yàn)楹瘮?shù)Function<T, X>
周圍的類型擦除將這些方法簽名轉(zhuǎn)換為重復(fù)。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的情況下,它們的簽名可能會(huì)變得含糊不清。例如,concatWith
以各種其他反應(yīng)性基類型作為參數(shù)(為了在底層實(shí)現(xiàn)中提供方便和性能優(yōu)勢(shì)),會(huì)出現(xiàn)一些重載:
Flowable<T> concatWith(Publisher<? extends T> other);
Flowable<T> concatWith(SingleSource<? extends T> other);
Publisher
和SingleSource
都以函數(shù)接口的形式出現(xiàn)(帶有一個(gè)抽象方法的類型),并可能鼓勵(lì)用戶嘗試提供lambda表達(dá)式:
someSource.concatWith(s -> Single.just(2))
.subscribe(System.out::println, Throwable::printStackTrace);
不幸的是,這種方法不起作用,示例根本不打印2
。 實(shí)際上,從2.1.10版本開始,它甚至不能編譯,因?yàn)橹辽儆?個(gè)concatWith
重載存在,編譯器發(fā)現(xiàn)上面的代碼不明確。
在這種情況下,用戶可能希望延遲一些計(jì)算,直到someSource
完成,因此正確的明確操作符應(yīng)該是defer
:
someSource.concatWith(Single.defer(() -> Single.just(2)))
.subscribe(System.out::println, Throwable::printStackTrace);
有時(shí),添加后綴是為了避免邏輯歧義,這些歧義可能會(huì)編譯,但會(huì)在流中產(chǎn)生錯(cuò)誤的類型:
Flowable<T> merge(Publisher<? extends Publisher<? extends T>> sources);
Flowable<T> mergeArray(Publisher<? extends T>... sources);
當(dāng)函數(shù)接口類型涉及到類型參數(shù)T
時(shí),這也會(huì)變得含糊不清。
錯(cuò)誤處理
數(shù)據(jù)流可能失敗,此時(shí)錯(cuò)誤被發(fā)送到使用者。 但是,有時(shí)多個(gè)源可能會(huì)失敗,這時(shí)可以選擇是否等待所有源完成或失敗。為了表示這種機(jī)會(huì),許多操作符的名稱都添加了DelayError
單詞的后綴(而其他操作符的重載中則添加了delayError
或delayErrors
boolean標(biāo)志):
Flowable<T> concat(Publisher<? extends Publisher<? extends T>> sources);
Flowable<T> concatDelayError(Publisher<? extends Publisher<? extends T>> sources);
當(dāng)然,各種各樣的后綴可能出現(xiàn)在一起:
Flowable<T> concatArrayEagerDelayError(Publisher<? extends T>... sources);
基類vs基類型
由于基類上的靜態(tài)方法和實(shí)例方法的數(shù)量太多,因此可以認(rèn)為基類很重。 RxJava 2的設(shè)計(jì)深受響應(yīng)流規(guī)范的影響,因此,該庫為每種響應(yīng)類型提供了一個(gè)類和一個(gè)接口:
Type | Class | Interface | Consumer |
---|---|---|---|
0..N backpressured | Flowable |
Publisher 1
|
Subscriber |
0..N unbounded | Observable |
ObservableSource 2
|
Observer |
1 element or error | Single |
SingleSource |
SingleObserver |
0..1 element or error | Maybe |
MaybeSource |
MaybeObserver |
0 element or error | Completable |
CompletableSource |
CompletableObserver |
1The org.reactivestreams.Publisher
是外部響應(yīng)流庫的一部分。它是通過受響應(yīng)流規(guī)范控制的標(biāo)準(zhǔn)化機(jī)制與其他響應(yīng)庫進(jìn)行交互的主要類型。
2接口的命名約定是將Source
追加到半傳統(tǒng)的類名中。 因?yàn)?code>Publisher是由響應(yīng)流庫提供的,所以沒有FlowableSource
(而且子類型對(duì)互操作也沒有幫助)。 然而,這些接口并不是響應(yīng)流規(guī)范意義上的標(biāo)準(zhǔn)接口,而且目前僅針對(duì)RxJava。