Outline
[TOC]
前言
在上一節中, 我們提到了Flowable
和Backpressure
背壓, 本來這一節的確是想講這兩個東西的,可是寫到一半感覺還是差點火候,感覺時機未到, 因此,這裡先來做個準備工作, 先帶大家學習zip
這個操作符, 這個操作符也是比較牛逼的東西了, 涉及到的東西也比較多, 主要是一些細節上的東西太多, 通過學習這個操作符,可以為我們下一節的Backpressure
做個鋪墊.
正題
照慣例我們還是先貼上一下比較正式的解釋吧.
Zip
通過一個函式將多個Observable傳送的事件結合到一起,然後傳送這些組合到一起的事件. 它按照嚴格的順序應用這個函式。它只發射與發射資料項最少的那個Observable一樣多的資料。
我們再用通俗易懂的圖片來解釋一下:
從這個圖中可以看見, 這次上游和以往不同的是, 我們有兩根水管了.
其中一根水管負責傳送圓形事件
, 另外一根水管負責傳送三角形事件
, 通過Zip操作符, 使得圓形事件
和三角形事件
合併為了一個矩形事件
.
下面我們再來看看分解動作:
通過分解動作我們可以看出:
- 組合的過程是
分別從
兩根水管裡各取出一個事件
來進行組合, 並且一個事件只能被使用一次,
組合的順序是嚴格按照事件傳送的順利
來進行的, 也就是說不會出現圓形1
事件和三角形B
事件進行合併, 也不可能出現圓形2
和三角形A
進行合併的情況. - 最終
下游收到的事件數量
是和上游中傳送事件最少的那一根水管的事件數量
相同. 這個也很好理解, 因為是從每一根水管
裡取一個事件來進行合併,最少的
那個肯定就最先取完
, 這個時候其他的水管儘管還有事件
, 但是已經沒有足夠的事件來組合了, 因此下游就不會收到剩餘的事件了.
分析了大概的原理, 我們還是勞逸結合, 先來看看實際中的程式碼怎麼寫吧:
Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "emit 1");
emitter.onNext(1);
Log.d(TAG, "emit 2");
emitter.onNext(2);
Log.d(TAG, "emit 3");
emitter.onNext(3);
Log.d(TAG, "emit 4");
emitter.onNext(4);
Log.d(TAG, "emit complete1");
emitter.onComplete();
}
});
Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.d(TAG, "emit A");
emitter.onNext("A");
Log.d(TAG, "emit B");
emitter.onNext("B");
Log.d(TAG, "emit C");
emitter.onNext("C");
Log.d(TAG, "emit complete2");
emitter.onComplete();
}
});
Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {
@Override
public String apply(Integer integer, String s) throws Exception {
return integer + s;
}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String value) {
Log.d(TAG, "onNext: " + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError");
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});複製程式碼
我們分別建立了兩個上游水管, 一個傳送1,2,3,4,Complete, 另一個傳送A,B,C,Complete, 接著用Zip把發出的事件組合, 來看看執行結果吧:
D/TAG: onSubscribe
D/TAG: emit 1
D/TAG: emit 2
D/TAG: emit 3
D/TAG: emit 4
D/TAG: emit complete1
D/TAG: emit A
D/TAG: onNext: 1A
D/TAG: emit B
D/TAG: onNext: 2B
D/TAG: emit C
D/TAG: onNext: 3C
D/TAG: emit complete2
D/TAG: onComplete複製程式碼
結果似乎是對的... 但是總感覺什麼地方不對勁...
哪兒不對勁呢, 為什麼感覺是水管一傳送完了之後, 水管二才開始傳送啊? 到底是不是呢, 我們來驗證一下:
Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "emit 1");
emitter.onNext(1);
Thread.sleep(1000);
Log.d(TAG, "emit 2");
emitter.onNext(2);
Thread.sleep(1000);
Log.d(TAG, "emit 3");
emitter.onNext(3);
Thread.sleep(1000);
Log.d(TAG, "emit 4");
emitter.onNext(4);
Thread.sleep(1000);
Log.d(TAG, "emit complete1");
emitter.onComplete();
}
});
Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.d(TAG, "emit A");
emitter.onNext("A");
Thread.sleep(1000);
Log.d(TAG, "emit B");
emitter.onNext("B");
Thread.sleep(1000);
Log.d(TAG, "emit C");
emitter.onNext("C");
Thread.sleep(1000);
Log.d(TAG, "emit complete2");
emitter.onComplete();
}
});
Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {
@Override
public String apply(Integer integer, String s) throws Exception {
return integer + s;
}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String value) {
Log.d(TAG, "onNext: " + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError");
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});複製程式碼
這次我們在每傳送一個事件之後加入了一秒鐘的延時, 來看看執行結果吧, 注意這是個GIF圖:
(貼心的我怕大家看不清楚, 特意調成了老年字型呢)
阿西吧, 好像真的是先傳送的水管一再傳送的水管二呢, 為什麼會有這種情況呢? 因為我們兩根水管都是執行在同一個執行緒裡, 同一個執行緒裡執行程式碼肯定有先後順序呀.
因此我們來稍微改一下, 不讓他們在同一個執行緒, 不知道怎麼切換執行緒的, 請掉頭看前面幾節.
Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "emit 1");
emitter.onNext(1);
Thread.sleep(1000);
Log.d(TAG, "emit 2");
emitter.onNext(2);
Thread.sleep(1000);
Log.d(TAG, "emit 3");
emitter.onNext(3);
Thread.sleep(1000);
Log.d(TAG, "emit 4");
emitter.onNext(4);
Thread.sleep(1000);
Log.d(TAG, "emit complete1");
emitter.onComplete();
}
}).subscribeOn(Schedulers.io());
Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
Log.d(TAG, "emit A");
emitter.onNext("A");
Thread.sleep(1000);
Log.d(TAG, "emit B");
emitter.onNext("B");
Thread.sleep(1000);
Log.d(TAG, "emit C");
emitter.onNext("C");
Thread.sleep(1000);
Log.d(TAG, "emit complete2");
emitter.onComplete();
}
}).subscribeOn(Schedulers.io());
Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {
@Override
public String apply(Integer integer, String s) throws Exception {
return integer + s;
}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String value) {
Log.d(TAG, "onNext: " + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError");
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});複製程式碼
好了, 這次我們讓水管都在IO執行緒裡傳送事件, 再來看看執行結果:
D/TAG: onSubscribe
D/TAG: emit A
D/TAG: emit 1
D/TAG: onNext: 1A
D/TAG: emit B
D/TAG: emit 2
D/TAG: onNext: 2B
D/TAG: emit C
D/TAG: emit 3
D/TAG: onNext: 3C
D/TAG: emit complete2
D/TAG: onComplete複製程式碼
GIF圖:
誒! 這下就對了嘛, 兩根水管同時開始傳送, 每傳送一個, Zip就組合一個, 再將組合結果傳送給下游.
不對呀! 可能細心點的朋友又看出端倪了, 第一根水管明明傳送了四個資料+一個Complete, 之前明明還有的, 為啥到這裡沒了呢?
這是因為我們之前說了, zip傳送的事件數量跟上游中傳送事件最少的那一根水管的事件數量是有關的, 在這個例子裡我們第二根水管只傳送了三個事件然後就傳送了Complete, 這個時候儘管第一根水管還有事件4
和事件Complete
沒有傳送, 但是它們發不傳送還有什麼意義呢? 所以本著節約是美德的思想, 就乾脆打斷它的狗腿, 不讓它發了.
至於前面的例子為什麼會傳送, 剛才不是已經說了是!在!同!一!個!線!程!裡!嗎!!!!再問老子打死你!
有好事的程式設計師可能又要問了, 那我不傳送Complete呢? 答案是顯然的, 上游會繼續傳送事件, 但是下游仍然收不到那些多餘的事件. 不信你可以試試.
實踐
學習了Zip的基本用法, 那麼它在Android有什麼用呢, 其實很多場景都可以用到Zip. 舉個例子.
比如一個介面需要展示使用者的一些資訊, 而這些資訊分別要從兩個伺服器介面中獲取, 而只有當兩個都獲取到了之後才能進行展示, 這個時候就可以用Zip了:
首先分別定義這兩個請求介面:
public interface Api {
@GET
Observable<UserBaseInfoResponse> getUserBaseInfo(@Body UserBaseInfoRequest request);
@GET
Observable<UserExtraInfoResponse> getUserExtraInfo(@Body UserExtraInfoRequest request);
}複製程式碼
接著用Zip來打包請求:
Observable<UserBaseInfoResponse> observable1 =
api.getUserBaseInfo(new UserBaseInfoRequest()).subscribeOn(Schedulers.io());
Observable<UserExtraInfoResponse> observable2 =
api.getUserExtraInfo(new UserExtraInfoRequest()).subscribeOn(Schedulers.io());
Observable.zip(observable1, observable2,
new BiFunction<UserBaseInfoResponse, UserExtraInfoResponse, UserInfo>() {
@Override
public UserInfo apply(UserBaseInfoResponse baseInfo,
UserExtraInfoResponse extraInfo) throws Exception {
return new UserInfo(baseInfo, extraInfo);
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<UserInfo>() {
@Override
public void accept(UserInfo userInfo) throws Exception {
//do something;
}
});複製程式碼
好了, 本次的教程就到這裡吧. 又到週末鳥, 下週見.