RxJava高級進階--lift操作符

之前幾篇文章是在爲這篇文章作鋪墊。關於RxJava的核心思想其實可以說就在於 lift() 。

這篇文章只分析 lift 的精髓。

其實RxJava的設計者認爲開發者不應該親自去設計 rx api,因爲在不理解 lift 的情況下非常容易導致難以分析的錯誤。 這也是爲什麼使用RxJava的人會發現這玩意提供的默認api竟然有那麼多,而且有些還基本長的差不多。 比如just/from,這倆基本是一回事。 這些封裝就是爲了最大幅度的覆蓋開發中的場景,避免開發者自定義api。

學習lift的過程有點像學習Android的Framework。不深入研究它一點問題也沒有,熟悉基本原理照樣能寫出很好的app。但是當你要做一些騷操作的話就發現fw是一個繞不過去的樁。而在熟悉了framework之後,你會發現自己的技術突然有了突飛猛進的變化。可能這就是有些開發者所說的內功修煉吧。

閒言少敘,書歸正傳。

回顧flatmap

之前我們說過flatmap是研究 lift 的切入點,也因此花了大量時間去說它。現在到了發揮它的光和熱的時候了。 先看下面這段簡單的代碼,它將一個數組中的字符串派發給 observer。

String[] provinces = new String[]{"Guangdong", "Chongqing", "Shandong"};
Observer<String> observer = new Observer<String>() {
    ....
    @Override
    public void onNext(String o) {
        //GET provinces
    }
};
Observable.from(provinces)
        .subscribe(observer);

這裏插句話,我們這裏不用之前的weather的demo的原因是爲了減少多餘的代碼對理解 lift 的影響。

現在抽象一個層次理解這段代碼。provinces是異步源,observer是原始的接收者,它會接收到各個省的字符串。 現在我們要修改一下邏輯,讓observer接收到的是各個省下面的所有城市,該怎麼辦?

這裏就用到 flatmap,我們之前說過flatmap可以理解爲一對多的變換,修改後的代碼變成下面這樣。

private Observable<String> getCitiesFromProvince(String province) {
    ...
}
Observable.from(provinces)
        .flatMap(new Func1<String, Observable<String>>() {
            @Override
            public Observable<String> call(String s) {
                return getCitiesFromProvince(s);
            }
        })
        .subscribe(observer);

這裏定義了一個方法 getCitiesFromProvince, 它是個異步調用,返回某個省下面的所有城市, 代碼稍微不很嚴謹,實際開發中它應該是個異步方法,需要在鏈式調用中加上線程切換。這裏也爲了方便理解省去這些步驟。

現在思考一個問題,observer所訂閱的還是原來的異步源嗎?

lift 在 flatmap 中的作用

這是個很有意思的問題,原始異步源沒變,最終的observer也沒變,但是他們的訂閱關係改變了嗎?

當然改變了。 展開來說,observe所訂閱的不再是原始的異步源了,在原始異步源和observer中間插入了一個lift操作, lift生成一個新的observer和observable, 爲了方便理解這裏稱爲 代理異步源 和 代理接受者, 原始observer所訂閱的是代理異步源, 原始異步源所派發的目標則變成了代理接受者。

上面這段話是全文的重點,理解了它也就理解了 lift 幹了什麼事。我發現很多被 lift 勸退的RxJava學習者都是因爲現有的學習資料對 lift 的解釋不夠清晰直觀。如果一上來就先從代碼去理解它,可能會事倍功半。

下面的動圖能幫助理解lift。

lift 原理

把上面 flatmap 的源碼精簡之後是下面這樣一個調用邏輯,

public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func) {
    ...
    return merge(map(func));
}

public static <T> Observable<T> merge(Observable<? extends Observable<? extends T>> source) {
    ...
    return source.lift(OperatorMerge.<T>instance(false));
}

可以看到最後會調用 lift 做一個變換,注意 lift 的主體source不是原來的異步源(observable), 而是 map(func()) 所生成的 observable,也就是代理異步源。

lift的源碼有點複雜,我們把它精簡一下

public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);//<-- onSubscribe.call()調用的是原始異步源的call
        }
    });
}

這裏分兩部分解析, lift的返回__ __lift中 call 裏面的 newSubscriber

lift的返回是一個 Observable 對象。結合上文所說的,這就是生成的代理異步源,我們原始的 observer 所訂閱的對象會變成代理異步源。

newSubscriber是什麼呢? 其實 newSubscriber 就是上文說的代理接受者。 注意註釋的那行代碼,

onSubscribe.call(newSubscriber);

不要被名字迷惑,onSubscribe.call 是個接口調用,onSubscribe就是原始異步源。 也即是說,這裏調用了原始異步源的 call,把原始異步源和newSubscriber做一個綁定, 在這之後,原始異步源會把結果發給代理接受者,也就是 newSubscriber。

爲什麼不建議用 lift

雖然 lift 也是開放api的其中一個,但是設計者不建議開發者對它做擴展。

有的人就要噴我了,看了這麼長的一篇東西結果說不建議用?逗我麼? 要明白這篇東西的目的是理解RxJava的核心變換,而不是學習怎麼用 lift()擴展自定義操作符。

從我的理解來說,不建議用lift的其中一個原因是它會導致流式代碼的閱讀性下降。 怎麼理解這句話呢,比如看下面的代碼

observable.map(...).filter(...).take(5).lift(new OperatorA()).subscribe(new Subscriber())

看起來這是個常見的RxJava流式調用, 數據從左到右流動,但! 這個理解是錯的。

不能明白?我教你。 還記得 lift 會產生一個新的 Observable嗎?看看 lift()的返回值。

public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
    return unsafeCreate(new OnSubscribeLift<T, R>(onSubscribe, operator));
}

首先把 lift 的左邊和右邊看成兩個部分。 lift() 的右邊訂閱的是 lift產生的 代理異步源,也就是這串東西,

observable.map(...).filter(...).take(5).lift(new OperatorA())

把它看成一個整體,上面的代碼簡化成一個代理異步源

proxyObservable.subscribe(new Subscriber())

然後還記得代理異步源負責接收原始異步源派發的數據這句話嗎, 也就是說左邊這個 proxyObservable(代理異步源),它的數據是從lift裏面的 new OperatorA() 中來的。

繞暈了? 所以非常不建議使用 lift 做騷操作。

聽說過下流嗎

這裏的下流不是那種下流啦… RxJava中的流有上流和下流的概念,當你對RxJava有足夠的瞭解就會涉及到這個東西。 比如這段代碼,

observable.map(...).filter(...).take(5).subscribe(new Subscriber())

這就是一個標準的從左到右的流。

但加入lift之後就不一樣了,

observable.map(...).filter(...).take(5).lift(new OperatorA()).subscribe(new Subscriber())

把lift()左右分開,lift()的右邊是一個常規的從左到右的流,也叫下流, lift()的左邊則是一個從右到左的流,也叫上流。

在RxJava中有兩個專門的名詞用來描述這種關係,

  • UpStream
  • DownStream 所以以後你看到UpStream和DownStream就明白是怎麼回事了吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章