RxJava原始碼初探

楚巖發表於2016-01-29

一、前言

RxJava是用java實現的ReactiveX(Reactive Extensions)框架開源庫。ReactiveX則是大名鼎鼎的響應式程式設計。而響應式程式設計和觀察者模式緊緊的相關聯。在看RxJava的原始碼中,分析起來會有點麻煩,所以才有了這篇文章,和對這個有興趣的同學一起窺探一二。

二、觀察者模式

2.1 基本原理

觀察者模式是物件的行為模式,又叫釋出-訂閱(Publish/Subscribe)模式即讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。在JDK中已有對觀察者模式的封裝:Observable.java/Observer.java(請自行對照JDK原始碼看看,下面會提出2個問題):
觀察者模式圖。

2.2 jdk實現版本

在JDK的實現版本中,Observable是被觀察者,有一個Vector陣列來存放觀察者,當被觀察者事件改變時,呼叫notifyObservers來通知所有的觀察者,每個觀察者呼叫自己的update來做相應的更新操作。由於這個模式在JDK中實現的比較早(JDK1.0),有2個地方值得思考:
1、觀察者陣列使用Vector,為什麼不使用List?是否有替代品?
由於觀察者陣列必須考慮到執行緒安全(比如在被觀察者發出通知的那一刻,同時新增新的觀察者,或者刪除某個觀察者等操作會讓被觀察者不知道到底通知哪些觀察者,即會引發執行緒安全),所以JDK版本中在對vector進行操作的時候,都會加上synchronized且在通知觀察者的時候反向遍歷陣列,以此來保證執行緒安全。
(1)為什麼不用List?
這裡可以看到通知觀察者的時候需要反向遍歷陣列,來保證如果是發出通知時有新的觀察者進來,新的觀察者不會收到當前通知。如果使用List反向遍歷會比使用陣列更加複雜。
(2)是否有替代品?
這裡的Vector操作滿足(a)讀大於寫,(b)執行緒安全,(c)寫時複製,所以可以用CopyOnWriteArrayList來代替,代替後實現程式碼會更加的簡潔明瞭。

2、通知方法,通知每個觀察者的時候沒有使用try—catch,是否可以加上?
JDK observable通知方法notifyObservers程式碼實現:

for (int i = arrLocal.length-1; i>=0; i--)
    ((Observer)arrLocal[i]).update(this, arg);

初看到這個段程式碼時,會有疑惑這個迴圈中沒有對update加try-catch,當某個觀察者的更新操作拋異常會導致其他的觀察者更新失敗。
那麼如果我們加上try-catch會發生什麼呢,雖然能夠保證每個觀察者都能做更新操作,但是一旦某個觀察者有異常,被被觀察者給捕獲了,而被觀察者捕獲後又不知道交給誰,怎麼處理,會導致程式碼編寫者以為所有的觀察者都正常執行了,所以在實現Observer的update操作時需要觀察者自己加上try-catch。

三、響應式程式設計

3.1 響應式程式設計的特點

上面有點跑了題。迴歸正傳,有了觀察者模式的實現做鋪墊,對我們理解響應式程式設計原理會有很大的幫助。相同點,都是多個觀察者觀察一個被觀察者的狀態,觀察者對狀態變化做出自己的處理。不同點,從我目前看到原始碼的程度上來看,我覺得觀察者模式和響應式程式設計的區別主要有以下幾個:

  • 1 觀察者模式是Observable-Observer,如果我們要在觀察者做操作前,對資料做一些其他的處理怎麼辦?觀察者模式無法解耦,只能在observer的update操作中來做。而響應式程式設計是Obsevable-Operator1-OperatorN-Observer,可以很好的解耦控制資料流操作。
  • 2 響應式程式設計通過鏈式呼叫,讓使用者在程式碼流程上能夠更加清晰的掌控(即使是非同步操作)。
  • 3 觀察者模式無法處理觀察者的異常,需要使用者自己加try-catch結構。而響應式程式設計提供了另一種方案,使用者不需要使用try-catch只需實現錯誤處理方法就可以做到。
  • 4 觀察者模式若需要使用多執行緒,需要使用者在observer中實現多執行緒操作,也就是將觀察者和任務排程糅雜在一起。而響應式程式設計對觀察者和任務排程解耦,可以通過Schedulers,來處理執行緒排程問題。
  • 5 觀察者模式,觀察者無法處理自己的呼叫超時問題,響應式程式設計則可以設定觀察者的排程超時機制。
  • 6 響應式程式設計提供阻塞式(BlockingObservable)和非阻塞式Observable的呼叫

3.2 RxJava原始碼分析

上文講了RxJava框架相比觀察者模式的各種優點,現在我們來分析原始碼,由於RxJava的原始碼比較龐大,本篇部落格只分析RxJava的基本原理,即區別中的第一點和第二點。對於其他的點會在另起blog來分析。

3.2.1 呼叫展現

好了,分析RxJava的基本原理前,我們先上一段測試程式碼(此處的Observable是RxJava中的,不是JDK中的):

Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("hello");
    }
}).map(new Func1<String, String>() {
    @Override
    public String call(String s) {
        return s + " word";
    }
}).subscribe(new Subscriber<String>() {
    @Override
    public void onCompleted() {
        System.out.println("completed");
    }
    @Override
    public void onError(Throwable e) {
        System.out.println("error");
    }
    @Override
    public void onNext(String s) {
        System.out.println("rx " + s);
    }
});

輸出:rx hello world
這種連.的寫法對看程式碼來說有點累,簡化下:

(1)Observable<String> observable1 = Observable.create(new OnSubscribe());
(2)Observable<String> observable2 = observable.map(new Func1());
(3)Subscription sub = observable2.subscribe(new Subscriber());

程式碼一簡化,我們就可以對上文說的3.1節中響應式程式設計的特點1:資料流操作和觀察者處理解耦的原理有所瞭解了。
這裡的解耦方式,其實就是在原被觀察者進行資料流操作(map)後生成一個新的被觀察者,觀察者其實訂閱的是資料流操作生成的被觀察者。
在整個過程中,可以看到三個重要的類:

  • Observable:被觀察者
  • OnSubscriber:Observable的成員變數,用來呼叫觀察者處理方法
  • Subscriber:觀察者

3.2.2 程式碼分析

RxJava具體怎麼處理,分3步看:

3.2.2.1 生成被觀察者Observable物件

第一行程式碼,(1)Observable observable1 = Observable.create(new OnSubscribe());
建立了一個被觀察者,進入原始碼

public final static <T> Observable<T> create(OnSubscribe<T> f) {
    return new Observable<T>(hook.onCreate(f));
}

/**
 * Invoked when Observable.subscribe is called.
 */
public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {
    // cover for generics insanity
}

即create為Observable的物件observable1建立了一個預設的鉤子hook和一個Onsubscribe物件。

3.2.2.2 資料流處理

第二行程式碼,(2)Observable observable2 = observable.map(new Func1());
建立了一個新的被觀察者2,進入原始碼:

public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
    return lift(new OperatorMap<T, R>(func));
}

這一行程式碼很關鍵,也很繞,2個動作
(1)生成了一個OperatorMap物件。這個物件call方法,引數為觀察者Subscriber物件;返回值是一個新的觀察者Subscriber物件。新的Subscriber物件的onNext方法先呼叫operator操作(map的call方法)的call方法,拿Operator操作返回的資料,再呼叫外部觀察者的onNext方法。很熟悉是嗎?上文說到的ReactiveX程式設計可以實現Observable-Operator1—–OperatorN-Observer,在這段程式碼中得到了體現:在呼叫Observer的OnNext方法前,會先呼叫OperatorN的call方法對資料處理,處理完成後的資料值,作為引數傳入Observer的OnNext方法。
我們需要注意的是,OperatorMap的call方法返回的是一個新的觀察者,新的觀察者通過OnNext方法將老的觀察者給連結起來的。那麼新的觀察者什麼時候訂閱被觀察者的呢?這個就是接下來的第(2)個動作:lift

public final class OperatorMap<T, R> implements Operator<R, T> {
    private final Func1<? super T, ? extends R> transformer;
    public OperatorMap(Func1<? super T, ? extends R> transformer) {
        this.transformer = transformer;
    }
    @Override
    public Subscriber<? super T> call(final Subscriber<? super R> o) {
        return new Subscriber<T>(o) {
            @Override
            public void onCompleted() {
                o.onCompleted();
            }
            @Override
            public void onError(Throwable e) {
                o.onError(e);
            }
            @Override
            public void onNext(T t) {
                try {
                    o.onNext(transformer.call(t));
                } catch (Throwable e) {
                    Exceptions.throwOrReport(e, this, t);
                }
            }
        };
    }

}

(2)呼叫了observable1的lift方法,這個方法用來幹什麼呢?

public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
    return new Observable<R>(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber<? super R> o) {
            Subscriber<? super T> st = hook.onLift(operator).call(o);
            st.onStart();
            onSubscribe.call(st); 
        });
}

原始碼略大我簡化了一下,其實就是原Observable(observable1)的lift方法建立了一個新的Observable物件(observable2),這個新的物件的OnSubscribe成員變數的call方法將map-OpreatorMap生成的觀察者傳入到了最開始的Observable的OnSubscirbe中進行處理。

過程再整理一下:

  • 1、 Create操作:建立一個Observable物件observable1,物件中的OnSubscribe用來響應Subscriber
  • 2、 OperatorMap操作生成新的觀察者,新的觀察者通過OnNext方法,先呼叫map的call方法處理資料,再呼叫1中的觀察者Subscriber的OnNext方法。
  • 3、lift 操作生成新的被觀察者observable2,observable2中的call方法通過observable1的成員變數OnSubscribe來呼叫2中生成的新的觀察者的call方法。

接下來就是最後一步了,什麼時候呼叫observable2的call方法呢?這個就是下面的介紹,被觀察者訂閱觀察者,在訂閱的時候進行呼叫。

3.2.2.3 觀察者訂閱

第三行程式碼,Subscription sub = observable2.subscribe(new Subscriber());
訂閱subscribe原始碼核心程式碼:

hook.onSubscribeStart(observable,observable.onSubscribe).call(subscriber);

其實這個就是呼叫observable2的OnSubscribe的call方法,引數是傳入的訂閱者。
說白了觀察者訂閱其實就是用被觀察者的subscribe方法訂閱觀察者,且呼叫被觀察者的OnSubscribe方法來呼叫觀察者的處理方法onNext。
這裡可能就會有疑問:為什麼是被觀察者來訂閱觀察者,而不是觀察者來訂閱被觀察者?
這種做法可以使整個過程使用連點(.)來完成從資料處理到訂閱者響應的所有流程,程式碼上會更加的清晰。

好了,是時候祭出大招了,映照下面這個圖,你會更加的清晰。
rxjavarxjava_8

如果,你覺得還不過癮,還有的沒有了解清楚。建議看程式碼。。。,下一篇部落格會介紹RxJava使用Scheduler多執行緒原始碼分析

相關參考文件:


相關文章