Rxjava2基礎

1、簡介

RxJava: Reactive Extensions for the JVM Rxjava : JVM的響應式擴展。

RxJava是響應式擴展的Java VM實現:通過使用可觀察序列組合異步和基於事件的程序的庫。
它擴展了observer模式以支持數據/事件序列,並添加了操作符,允許您以聲明的方式將序列組合在一起,同時抽象出對底層線程、同步、線程安全性和併發數據結構等方面的關注。

2、使用

第一步是將RxJava 2包導入項目中,在build.gradle中添加:

compile "io.reactivex.rxjava2:rxjava:2.x.y"  // 將x和y替換爲最新的版本號 ,此時最新爲2.1.16 。

樣例 Hello World

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

如果您的平臺不支持Java 8 lambdas(到目前爲止),您必須手動創建一個Consumer內部類:

import io.reactivex.functions.Consumer;

Flowable.just("Hello world")
  .subscribe(new Consumer<String>() {
      @Override public void accept(String s) {
          System.out.println(s);
      }
  });

3、Base classes

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.

一些術語

1、Upstream, downstream
RxJava中的dataflow由一個源、零個或多箇中間步驟組成,然後是一個數據使用者或組合器步驟(其中步驟負責以某種方式使用dataflow):

source.operator1().operator2().operator3().subscribe(consumer);

source.flatMap(value -> source.operator1().operator2().operator3());

在這裏,如果我們假設我們在operator2上,向左看向源,稱爲上游。向右看爲用戶/消費者,稱爲下游。當每個元素都寫在單獨的一行上時,這一點通常更爲明顯 :

source
  .operator1()
  .operator2()
  .operator3()
  .subscribe(consumer)

2、Objects in motion
在RxJava的文檔中,emission, emits, item, event, signal, data and message (發射、發出、項、事件、信號、數據和消息)被視爲同義詞,並表示沿着數據流移動的對象。

3、Backpressure
當數據流通過異步步驟運行時,每個步驟可能以不同的速度執行不同的事情。爲了避免過多地使用此類步驟(通常表現爲由於臨時緩衝或需要跳過/刪除數據而導致的內存使用量增加),應用了所謂的背壓(backpressure),這是一種流控制形式,在這種形式下,這些步驟(the steps)可以表示它們準備處理多少項。這樣就可以限制數據的內存使用,在這種情況下一個步驟(a step)通常都無法知道上游會發送多少條信息。
在RxJava中,Flowable class專門用於支持backpressure,而Observable則專門用於非backpressure操作(短序列、GUI交互等)。其他類型的,如Single, Maybe and Completable 不支持backpressure,也不應該支持 ; 總會有暫時存放一件物品的空間。
4、Assembly time(裝配時間)
在所謂的裝配時間內,應用不同的中間操作符來準備dataflow:

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

此時,數據還沒有流動,也沒有出現任何副作用。
5、Subscription time
當一個流(a flow)上調用了subscribe()也就在內部建立處理步驟鏈時,這是一個臨時狀態:

flow.subscribe(System.out::println)

這時會觸發訂閱副作用(參見doOnSubscribe)。有些源會在這種狀態下立即阻塞或開始發送項目。

5、Runtime
這是當流正在主動地發出項、錯誤或完成(emitting items, errors or completion)信號時的狀態:

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

這種樣式的鏈接方法稱爲a 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);

通常,您可以通過subscribeOn將計算或阻塞IO的操作移動到其他線程。一旦數據準備好,您可以確保它們在前臺或GUI線程上通過observeOn進行處理。

6、Schedulers
RxJava操作符不直接使用線程或ExecutorServices,而是使用所謂的調度器,這些調度器在統一的API後面提取併發源。RxJava 2的特性是可以通過調度器實用類訪問的幾個標準調度器。

  • Schedulers.computation() :在後臺對固定數量的專用線程進行計算密集型工作。大多數異步操作符都將其作爲默認調度器。
  • Schedulers.io():在一組動態變化的線程上運行I/O或阻塞操作
  • Schedulers.single():按順序和FIFO方式在單個線程上運行工作。
  • Schedulers.trampoline():在一個參與的線程中以順序和FIFO方式運行工作,通常用於測試目的。

這些都可以在所有JVM平臺上使用,但是一些特定的平臺,比如Android,有它們自己定義的典型調度器:AndroidSchedulers.mainThread(), SwingScheduler.instance() or JavaFXSchedulers.gui().

此外,還可以選擇將現有的執行程序(及其子類型,如ExecutorService)通過Schedulers.from(Executor)包裝到調度器中。例如,可以使用它來擁有一個更大但仍然固定的線程池(不同於 computation() 和io() 獨自地 )。

最後的代碼thread . sleep(2000);並不是偶然的。在RxJava中,默認的調度器運行在守護進程線程上,這意味着一旦Java主線程退出,它們都會被停止,後臺計算可能永遠不會發生。在這個示例場景中,休眠一段時間讓你可以看到在控制檯上的流有時間輸出。
7、Concurrency(併發性) within a flow
RxJava中的流本質上是連續的,根據它們可能同時運行 分爲處理階段:

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

這個示例流將computation Scheduler上的數字從1到10進行平方,並使用“主”線程(更準確地說,是blockingSubscribe的調用者線程)消費結果。但是,對這個流來說v -> v * v不是並行運行的;它在相同的計算線程上一個接一個地接收值1到10。
8、Parallel processing
實際上,RxJava中的並行性意味着獨立運行的流並將其結果合併到單個流中。操作符flatMap首先將從1到10的每一個數字映射到它自己的Flowable,運行它們各自的平方運算然後合併結果值。
但是,請注意,flatMap並不保證有任何順序,內部流的最終結果可能會交叉。可選擇的操作符:
concatMap 每次映射並運行一個內部流
concatMapEager 它“同時”運行所有內部流,但輸出流將按照創建這些內部流的順序。
另外,還有一個beta運算符flow .parallel()和 ParallelFlowable 類型,它們都有助於實現相同的並行處理模式:

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

並行性和併發性 (Concurrence) 是既相似又有區別的兩個概念,並行性是指兩個或多個事件在同一時刻發生;而併發性是指兩個或多個事件在同一時間間隔內發生。在多道程序環境下,併發性是指在一段時間內宏觀上有多個程序在同時運行,但在單處理機系統中每一時刻卻僅能有一道程序執行,故微觀上這些程序只能是分時地交替執行。倘若在計算機系統中有多個處理機,則這些可以併發執行的程序便可被分配到多個處理機上,實現並行執行,即利用每個處理機來處理一個可併發執行的程序,這樣,多個程序便可同時執行。

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

Continuations
有時,當一個item變得可利用的時,我們希望對它執行一些相關的計算。這有時被稱爲延續,根據應該發生的事情和涉及的類型,可能涉及到的各種操作符來完成。

Dependent
最典型的場景是給定一個值,調用另一個服務,等待其結果並繼續 :

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

通常,後面的序列也需要來自早期映射的值。這可以通過將the outer flatMap移動到the previous flatMap的內部部分來實現,例如:

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

這裏,原始值將在the inner flatMap中可用的,由lambda變量捕獲提供。

Non-dependent
在其他場景中,第一個源/dataflow的結果是不相關的,我們希望繼續使用一個準獨立的另一個源。在這裏,flatMap也可以工作:

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

然而,在這種情況下的延續是可以觀察到的,而不是更合適的單一。(這是可以理解的,因爲從flatMapSingle的角度來看,sourceObservable是一個多值的源,因此映射也可以產生多個值)。
通常情況下,通過使用Completable作爲中介人和它的運算符,然後用其他東西重新開始,會有一種表達能力更強(也更低開銷)的方法:

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

sourceObservable和someSingleSource之間的惟一依賴關係是,前者應該正常完成,以便後者被消費。
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())是在數據流還沒有運行時在組裝時計算的。我們需要推遲這個Single來源的評價,直到運行時完成主要來源:

AtomicInteger count = new AtomicInteger();

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

or

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可以返回單個。如果代碼示例保持不變,這將導致編譯時錯誤(但是,經常會出現關於缺少過載的錯誤消息)。
在這種情況下,修復轉換通常有兩個選項:1)轉換爲所需的類型,2)查找並使用支持不同類型的特定操作符的重載。

Converting to the desired type
每個響應基類都具有操作符,它們可以執行此類轉換,包括協議轉換,以匹配其他類型。下面的矩陣顯示了可用的轉換選項:
這裏寫圖片描述
1:當把一個多值源轉換成一個單值源時,我們應該決定應該將多個源值中的哪個作爲結果。
2:將可觀測的流轉換爲可觀測的流需要一個額外的決定:如何處理可觀測源的無約束流?通過BackpressureStrategy參數或通過標準的可流動操作符(如onBackpressureBuffer、onBackpressureDrop、onbackpressure - est),有幾種可用的策略(例如緩衝、刪除、保持最新),這些策略還允許進一步定製backpressure行爲。
3:當最多隻有一個源項時,背壓沒有問題,因爲它可以一直存儲到下游準備好消耗爲止。

Using an overload with the desired type
許多常用的操作符都有可以處理其他類型的重載。這些通常以目標類型的後綴命名:
這裏寫圖片描述
不能使用關鍵字
在原始的Rx.NET中,發出單個項然後完成的操作符稱爲Return(T)。因爲Java約定是用小寫字母開始方法名,所以這應該是return(T),這是Java中的關鍵字,因此不可用。因此,RxJava選擇將這個運算符命名爲just(T)。操作符交換機也有同樣的限制,必須命名爲switchOnNext。另一個例子是Catch,它被命名爲onErrorResumeNext。
類型擦除
許多期望用戶提供返回活性類型的函數的操作符不能被重載,因爲Function

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

Publisher和SingleSource都作爲函數接口(使用一個抽象方法的類型)出現,並可能鼓勵用戶嘗試提供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涉及時,這也可能變得不明確。

Error handling

數據流可能會失敗,這時錯誤將被髮送給使用者。但是,有時候多個源可能會失敗,這時就可以選擇是否等待它們全部完成或失敗。爲了指出這個時機,許多操作符的名稱都以DelayError詞作爲後綴(其他操作符的一個重載中有DelayError或delayErrors boolean標誌):

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 2的設計受到了響應式流規範的嚴重影響,因此,該庫爲每個響應式類型提供了一個類和一個接口:
這裏寫圖片描述
1org.reactivestreams.Publisher是外部反應流庫的一部分。它是通過受反應式流規範控制的標準機制與其他反應性庫交互的主要類型。
2接口的命名約定是將源附加到半傳統的類名。沒有FlowableSource,因爲發佈者是由反應流庫提供的(而且它的子類型也不能幫助進行互操作)。然而,這些接口在無反應流規範的意義上是不標準的,目前只針對RxJava

RxJava 2.0中backpressure(背壓)概念的理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章