RxJava操作符之組合操作符(六)

LeiHolmes發表於2019-03-04

前言

  上一篇文章我們學習了過濾類操作符,本篇我們將一起來學習RxJava組合類操作符。組合操作符主要是用來同時處理多個Observable,將他們進行組合建立出新的滿足我們需求的Observable,一起來看下都有哪些。
  

組合操作符

Merge

  merge操作符,將兩個Observable要發射的觀測序列合併為一個序列進行發射。按照兩個序列每個元素的發射時間先後進行排序,同一時間點發射的元素則是無序的。

//將一個傳送字母的Observable與傳送數字的Observable合併發射
final String[] words = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I"};
//字母Observable,每200ms發射一次
Observable<String> wordSequence = Observable.interval(200, TimeUnit.MILLISECONDS)
        .map(new Func1<Long, String>() {
            @Override
            public String call(Long position) {
                return words[position.intValue()];
            }
        })
        .take(words.length);
//數字Observable,每500ms發射一次
Observable<Long> numberSequence = Observable.interval(500, TimeUnit.MILLISECONDS).take(4);
Observable.merge(wordSequence, numberSequence)
        .subscribe(new Action1<Serializable>() {
            @Override
            public void call(Serializable serializable) {
                Log.e("rx_test", "merge:" + serializable.toString());
            }
        });複製程式碼

  輸出結果:

merge:A
merge:B
merge:0
merge:C
merge:D
merge:E
merge:1
merge:F
merge:G
merge:2
merge:H
merge:I
merge:3複製程式碼

  原理圖:

  merge操作符還有一種入參merge(Observable[]),可傳入含有多個Observable的集合,merge操作符也可將這多個Observable的序列合併後發射。

MergeDelayError

  mergeDelayError操作符,與merge功能類似,都是用來合併Observable的。不同之處在於mergeDelayError操作符在合併過程中發生異常的話不會立即停止合併,而會在所有元素合併發射完畢之後再發射異常。但發生異常的那個Observable就不會發射資料了。

//字母Observable,每200ms發射一次,模擬過程中產生一個異常
Observable<String> wordSequence = Observable.interval(200, TimeUnit.MILLISECONDS)
        .map(new Func1<Long, String>() {
            @Override
            public String call(Long position) {
                Long cache = position;
                if (cache == 3) {
                    cache = cache / 0;
                }
                return words[position.intValue()];
            }
        })
        .take(words.length);
//數字Observable,每500ms發射一次
Observable<Long> numberSequence = Observable.interval(500, TimeUnit.MILLISECONDS).take(4);
Observable.mergeDelayError(wordSequence, numberSequence)
        .subscribe(new Action1<Serializable>() {
            @Override
            public void call(Serializable serializable) {
                Log.e("rx_test", "mergeDelayError:" + serializable.toString());
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                Log.e("rx_test", "mergeDelayError:" + throwable.getMessage());
            }
        }, new Action0() {
            @Override
            public void call() {
                Log.e("rx_test", "mergeDelayError:onComplete");
            }
        });複製程式碼

  輸出結果:

mergeDelayError:A
mergeDelayError:B
mergeDelayError:0
mergeDelayError:C
mergeDelayError:1
mergeDelayError:2
mergeDelayError:3
mergeDelayError:divide by zero複製程式碼

  由輸出結果可看出,wordSequence在發射到C時丟擲了一個異常,停止發射其剩下的資料,但合併沒有停止。合併完成之後這個異常才被髮射了出來。
  原理圖:

Concat

  concat操作符,將多個Obserbavle發射的資料進行合併後發射,類似於merge操作符。但concat操作符是將Observable依次發射,是有序的。

Observable<String> wordSequence = Observable.just("A", "B", "C", "D", "E");
Observable<Integer> numberSequence = Observable.just(1, 2, 3, 4, 5);
Observable<String> nameSequence = Observable.just("Sherlock", "Holmes", "Xu", "Lei");
Observable.concat(wordSequence, numberSequence, nameSequence)
        .subscribe(new Action1<Serializable>() {
            @Override
            public void call(Serializable serializable) {
                Log.e("rx_test", "concat:" + serializable.toString());
            }
        });複製程式碼

  輸出結果:

concat:A
concat:B
concat:C
concat:D
concat:E
concat:1
concat:2
concat:3
concat:4
concat:5
concat:Sherlo
concat:Holmes
concat:Xu
concat:Lei複製程式碼

  原理圖:

Zip

  zip(Observable, Observable, Func2)操作符,根據Func2中的call()方法規則合併兩個Observable的資料項併發射。
  注意:若其中一個Observable資料傳送結束或出現異常後,另一個Observable也會停止發射資料。

Observable<String> wordSequence = Observable.just("A", "B", "C", "D", "E");
Observable<Integer> numberSequence = Observable.just(1, 2, 3, 4, 5, 6);
Observable.zip(wordSequence, numberSequence, new Func2<String, Integer, String>() {
    @Override
    public String call(String s, Integer integer) {
        return s + integer;
    }
}).subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
        Log.e("rx_test", "zip:" + s);
    }
});複製程式碼

  輸出結果:

zip:A1
zip:B2
zip:C3
zip:D4
zip:E5複製程式碼

  由輸出結果可看出numberSequence觀測序列最後的6並沒有發射出來,由於wordSequence觀測序列已發射完所有資料,所以組合序列也停止發射資料了。
  原理圖:

StartWith

  startWith操作符,用於在源Observable發射的資料前,插入指定的資料併發射。

Observable.just(4, 5, 6, 7)
        .startWith(1, 2, 3)
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                Log.e("rx_test", "startWith:" + integer);
            }
        });複製程式碼

  輸出結果:

startWith:1
startWith:2
startWith:3
startWith:4
startWith:5
startWith:6
startWith:7複製程式碼

  原理圖:

  startWith還有兩種入參:

  • startWith(Iterable):可在源Observable發射的資料前插入Iterable資料併發射。
  • startWith(Observable):可在源Observable發射的資料前插入另一Observable發射的資料併發射。

    SwitchOnNext

      switchOnNext操作符,用來將一個發射多個小Observable的源Observable轉化為一個Observable,然後發射多個小Observable所發射的資料。若小Observable正在發射資料時,源Observable又發射了新的小Observable,則前一個小Observable還未發射的資料會被拋棄,直接發射新的小Observable所發射的資料,上例子。
    “`java
    //每隔500ms產生一個Observable
    Observable> observable = Observable.interval(500, TimeUnit.MILLISECONDS)

      .map(new Func1<Long, Observable<Long>>() {
          @Override
          public Observable<Long> call(Long aLong) {
              //每隔200毫秒產生一組資料(0,10,20,30,40)
              return Observable.interval(200, TimeUnit.MILLISECONDS)
                      .map(new Func1<Long, Long>() {
                          @Override
                          public Long call(Long aLong) {
                              return aLong * 10;
                          }
                      }).take(5);
              }
      }).take(2);複製程式碼

Observable.switchOnNext(observable)
.subscribe(new Action1() {
@Override
public void call(Long aLong) {
Log.e(“rx_test”, “switchOnNext:” + aLong);
}
});

  輸出結果:
```java
switchOnNext:0
switchOnNext:10
switchOnNext:0
switchOnNext:10
switchOnNext:20
switchOnNext:30
switchOnNext:40複製程式碼

  由輸出結果發現第一個小Observable列印到10則停止了發射資料,說明其發射到10時,新的小Observable被建立了出來,第一個小Observable則被中斷髮射,開始發射新的小Observable的資料。
  原理圖:

CombineLatest

  combineLatest操作符,用於將兩個Observale最近發射的資料以Func2函式的規則進行組合併發射。

//引用merge的例子
final String[] words = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I"};
Observable<String> wordSequence = Observable.interval(300, TimeUnit.MILLISECONDS)
        .map(new Func1<Long, String>() {
            @Override
            public String call(Long position) {
                return words[position.intValue()];
            }
        })
        .take(words.length);
Observable<Long> numberSequence = Observable.interval(500, TimeUnit.MILLISECONDS)
        .take(5);
Observable.combineLatest(wordSequence, numberSequence,
        new Func2<String, Long, String>() {
            @Override
            public String call(String s, Long aLong) {
                return s + aLong;
            }
        })
        .subscribe(new Action1<Serializable>() {
            @Override
            public void call(Serializable serializable) {
                Log.e("rx_test", "combineLatest:" + serializable.toString());
            }
        });複製程式碼

  輸出結果:

combineLatest:A0
combineLatest:B0
combineLatest:C0
combineLatest:C1
combineLatest:D1
combineLatest:E1
combineLatest:E2
combineLatest:F2
combineLatest:F3
combineLatest:G3
combineLatest:H3
combineLatest:H4
combineLatest:I4複製程式碼

  如果將wordSequence與numberSequence的入參順序互換,輸出結果也會不同:

combineLatest:0A
combineLatest:0B
combineLatest:0C
combineLatest:1C
combineLatest:1D
combineLatest:2D
combineLatest:2E
combineLatest:2F
combineLatest:3F
combineLatest:3G
combineLatest:3H
combineLatest:4H
combineLatest:4I複製程式碼

  wordSequence每300ms發射一個字元,numberSequence每500ms發射一個數字。可能有些碼友不知道這個輸出結果怎麼來的,這個操作符確實不太好理解。我們來看一下這個原理圖就很清楚了。
  原理圖:

Join

  join(Observable, Func1, Func1, Func2)操作符,類似於combineLatest操作符,用於將ObservableA與ObservableB發射的資料進行排列組合。但join操作符可以控制Observable發射的每個資料的生命週期,在每個發射資料的生命週期內,可與另一個Observable發射的資料按照一定規則進行合併,來看下join的幾個入參。

  • Observable:需要與源Observable進行組合的目標Observable。
  • Func1:接收從源Observable發射來的資料,並返回一個Observable,這個Observable的宣告週期決定了源Obsrvable發射出來的資料的有效期;
  • Func1:接收目標Observable發射來的資料,並返回一個Observable,這個Observable的宣告週期決定了目標Obsrvable發射出來的資料的有效期;
  • Func2:接收從源Observable和目標Observable發射出來的資料,並將這兩個資料按自定的規則組合後返回。
    //產生字母的序列,週期為1000ms
    String[] words = new String[]{"A", "B", "C", "D", "E", "F", "G", "H"};
    Observable<String> observableA = Observable.interval(1000, TimeUnit.MILLISECONDS)
          .map(new Func1<Long, String>() {
              @Override
              public String call(Long aLong) {
                  return words[aLong.intValue()];
              }
          }).take(8);
    //產0,1,2,3,4,5,6,7的序列,延時500ms發射,週期為1000ms
    Observable<Long> observableB = Observable.interval(500, 1000, TimeUnit.MILLISECONDS)
          .map(new Func1<Long, Long>() {
              @Override
              public Long call(Long aLong) {
                  return aLong;
              }
          }).take(words.length);
    //join
    observableA.join(observableB,
          new Func1<String, Observable<Long>>() {
              @Override
              public Observable<Long> call(String s) {
                  //ObservableA發射的資料有效期為600ms
                  return Observable.timer(600, TimeUnit.MILLISECONDS);
              }
          },
          new Func1<Long, Observable<Long>>() {
              @Override
              public Observable<Long> call(Long aLong) {
                  //ObservableB發射的資料有效期為600ms
                  return Observable.timer(600, TimeUnit.MILLISECONDS);
              }
          },
          new Func2<String, Long, String>() {
              @Override
              public String call(String s, Long aLong) {
                  return s + aLong;
              }
          }
    ).subscribe(new Action1<String>() {
      @Override
      public void call(String s) {
          Log.e("rx_test", "join:" + s);
      }
    });複製程式碼

      join操作符的組合方式類似於數學上的排列組合規則,以ObservableA為基準源Observable,按照其自身週期發射資料,且每個發射出來的資料都有其有效期。而ObservableB每發射出來一個資料,都與A發射出來的並且還在有效期內的資料按Func2函式中的規則進行組合,B發射出來的資料也有其有效期。最後再將結果發射給觀察者進行處理。
      輸出結果:

    join:A0
    join:A1
    join:B1
    join:B2
    join:C2
    join:C3
    join:D3
    join:D4
    join:E4
    join:E5
    join:F5
    join:F6
    join:G6
    join:G7
    join:H7複製程式碼

      原理圖:

GroupJoin

  groupJoin操作符,類似於join操作符,區別在於第四個引數Func2的傳入函式不同,對join之後的結果包裝了一層小的Observable,便於使用者再次進行一些過濾轉換等操作再發射給Observable。

observableA.groupJoin(observableB,
        new Func1<String, Observable<Long>>() {
            @Override
            public Observable<Long> call(String s) {
                return Observable.timer(600, TimeUnit.MILLISECONDS);
            }
        },
        new Func1<Long, Observable<Long>>() {
            @Override
            public Observable<Long> call(Long aLong) {
                return Observable.timer(600, TimeUnit.MILLISECONDS);
            }
        },
        new Func2<String, Observable<Long>, Observable<String>>() {
            @Override
            public Observable<String> call(final String s, Observable<Long> longObservable) {
                return longObservable.map(new Func1<Long, String>() {
                    @Override
                    public String call(Long aLong) {
                        return s + aLong;
                    }
                });
            }
        })
        .subscribe(new Action1<Observable<String>>() {
            @Override
            public void call(Observable<String> stringObservable) {
                stringObservable.subscribe(new Action1<String>() {
                    @Override
                    public void call(String s) {
                        Log.e("rx_test", "groupJoin:" + s);
                    }
                });
            }
        });複製程式碼

  輸出結果:

groupJoin:A0
groupJoin:A1
groupJoin:B1
groupJoin:B2
groupJoin:C2
groupJoin:C3
groupJoin:D3
groupJoin:D4
groupJoin:E4
groupJoin:E5
groupJoin:F5
groupJoin:F6
groupJoin:G6
groupJoin:G7
groupJoin:H7複製程式碼

  原理圖:

總結

  到此,本篇關於RxJava的常用組合類操作符就講解完畢了。通過以上四篇文章對RxJava四類操作符的學習,相信大家已經基本掌握RxJava如何使用了。實踐是檢驗真理的唯一標準,下一篇我們來一起上專案看看實踐中如何使用RxJava。
  技術渣一枚,有寫的不對的地方歡迎大神們留言指正,有什麼疑惑或者建議也可以在我Github上RxJavaDemo專案Issues中提出,我會及時回覆。
  附上RxJavaDemo的地址:
  RxJavaDemo

相關文章