RxJava原始碼解析(一)從一個例子開始

PigCanFly發表於2017-09-18

注:本篇文章程式碼基於Rxjava1.x
RxJava是目前非常流行的一個響應是程式設計框架,它用了Java的語法特性來模擬出一套流式過程化的寫法,並可以通過執行緒排程器,非常方便的實現執行緒切換。本系列文章假設讀者已經是使用過Rxjava或者RxAndroid的開發者,如果你還未使用過,不妨看下下面的幾篇文章:
1.謎之RxJava
2.給 Android 開發者的 RxJava 詳解
3.深入淺出RxJava

本篇將使用一個非常簡單的例子做引線,引出在Rxjava中一些核心類和核心物件,如果你尚未使用過Rxjava,請在閱讀過上面幾篇文章後,編寫過一些Rxjava相關程式碼後再閱讀本文章。
我們來看下我們的例子:

Observable.just("str1", "str2", "str3", "str4")
        .map(new Func1<String, String>() {
            @Override
            public String call(String t) {
                // TODO Auto-generated method stub
                return "[" + t + "]";
            }
        }).subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                System.out.println("onCompleted ");
            }
            @Override
            public void onNext(String t) {
                System.out.println("onNext " + t);
            }

            @Override
            public void onError(Throwable e) {}
        });複製程式碼

上面的例子非常簡單,換成我們的自然語言可以分成以下步驟:
1.構建一個String[]陣列的Observable
2.通過對映方法map,將返回值對映成為"["+t+"]"格式
3.被一個訂閱者所訂閱
最後我們將在控制檯輸出:

output:
onNext [str1]
onNext [str2]
onNext [str3]
onNext [str4]
onCompleted

雖然是短短几行程式碼,簡單幾個類,但是已經包含了大部分RxJava中的核心元素,本章就以這個簡單的例子為引子,引出RxJava的基本體系結構和一些核心功能類。我們先來看下

Observable.just("str1", "str2", "str3", "str4")複製程式碼

這個方法的輸入是一堆陣列,輸出是一個Observable物件。實際上它就是一個靜態工廠,我們看下它的原始碼:

public static <T> Observable<T> just(T t1, T t2, T t3, T t4) {
        return from((T[])new Object[] { t1, t2, t3, t4 });
}// call from

public static <T> Observable<T> from(T[] array) {
        int n = array.length;
        if (n == 0) {
            return empty();
        } else
        if (n == 1) {
            return just(array[0]);//選擇構造方法
        }
        return unsafeCreate(new OnSubscribeFromArray<T>(array));//使用OnSubscribeFromArray為引數構造
    }複製程式碼

just程式碼中呼叫了from方法來執行構造,而在from方法中,會先進行陣列空判斷和長度判斷,目的是為了選擇不同的構造方法。最後我們所傳入的陣列物件,將被包裝成為一個OnSubscribeFromArray物件。而這個物件作為一個引數被unsafeCreate方法所呼叫。unsafeCreate是構造Observable最核心的方法,不論哪種方式的構造器最終都會呼叫到這個方法。我們現在看下這個方法的宣告:

public static <T> Observable<T> unsafeCreate(OnSubscribe<T> f) {
        return new Observable<T>(RxJavaHooks.onCreate(f));
    }複製程式碼

首先,我們傳入的OnSubscribe物件將被RxJavaHooks的onCreate給hook住,轉化為一個OnSubscribe物件。這裡,如果你對aop不陌生的話,相信這塊很好理解,實際上相當於你在構造Observable的時候做了一層攔截,或者說一次hook。我們不妨深入一點,看下RxJavaHooks裡面究竟到底做了什麼轉換:

//code RxJavaHooks.java
public static <T> Observable.OnSubscribe<T> onCreate(Observable.OnSubscribe<T> onSubscribe) {
        Func1<Observable.OnSubscribe, Observable.OnSubscribe> f = onObservableCreate;
        if (f != null) {
            return f.call(onSubscribe);
        }
        return onSubscribe;
    }複製程式碼

RxJavaHooks.onCreate方法用到了一個函式物件onObservableCreate。這裡所定義的函式物件很類似我們在動態語言中定義的閉包物件。我們看下onObservableCreate物件是怎麼被賦值的:

public class RxJavaHooks {
  static {
        init();
    }
  static void init() { 
      ...
      initCreate();
  }
static void initCreate() {
        ...
        onObservableCreate = new Func1<Observable.OnSubscribe, Observable.OnSubscribe>() {
            @Override
            public Observable.OnSubscribe call(Observable.OnSubscribe f) {
                return RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f);
            }
        };
     ...
}複製程式碼

RxJavaHooks類在類初始化的時候通過呼叫init->initCreate方法給onObservableCreate函式物件賦值。而賦值函式會呼叫

RxJavaPlugins.getInstance().getObservableExecutionHook().onCreate(f);複製程式碼

也就是說RxJavaHooks只是提供一個簡單的介面和初始化操作。實際呼叫者在RxJavaPlugins中。我們看下RxJavaPlugins.getObservableExecutionHook函式:

public RxJavaObservableExecutionHook getObservableExecutionHook() {
        if (observableExecutionHook.get() == null) {
            // check for an implementation from System.getProperty first
            Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, System.getProperties());//通過系統配置獲取一個RxJavaObservableExecutionHook物件。
            if (impl == null) {
                // nothing set via properties so initialize with default
                observableExecutionHook.compareAndSet(null, RxJavaObservableExecutionHookDefault.getInstance());
//如果沒有配置物件則使用預設物件
                // we don't return from here but call get() again in case of thread-race so the winner will always get returned
            } else {
                // we received an implementation from the system property so use it
                observableExecutionHook.compareAndSet(null, (RxJavaObservableExecutionHook) impl);
            }
        }
        return observableExecutionHook.get();
    }複製程式碼

實際上, getObservableExecutionHook()方法得到的物件也是一個單利,但是是非執行緒安全的,這段程式碼主要做以下事情:

1.如果RxJavaObservableExecutionHook物件不存在,會先通過呼叫getPluginImplementationViaProperty方法,也就是通過檢視系統配置引數檢視是否有實現類,如果有,將生成一個具體的RxJavaObservableExecutionHook例項返回
2.如果通過步驟1無法生成一個RxJavaObservableExecutionHook物件,將返回一個預設的RxJavaObservableExecutionHookDefault. getInstance()物件
3.最後將通過1,2獲取的物件記錄在全域性變數中

getObservableExecutionHook函式流程圖
getObservableExecutionHook函式流程圖

這裡引出一個問題,就是我們如何注入一個hook函式呢?這就需要深入到getPluginImplementationViaProperty的具體實現中去:

{//code getPluginImplementationViaProperty()
final String classSimpleName = pluginClass.getSimpleName();
        String pluginPrefix = "rxjava.plugin.";
        String defaultKey = pluginPrefix + classSimpleName + ".implementation";
...
}複製程式碼

首先,getPluginImplementationViaProperty會先定義一個key,這個key的基本結構為:rxjava.plugin.[classSimpleName].implementation。而這裡的classSimpleName依賴於我們傳入的pluginClass物件。我們回到剛才的呼叫鏈:

 Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, System.getProperties());複製程式碼

在呼叫getPluginImplementationViaProperty函式的時候,我們傳入的是一個RxJavaObservableExecutionHook型別,因此這裡的classSimpleName 值對應的應該是"RxJavaObservableExecutionHook",所以我們就得到了配置的key為:

"rxjava.plugin.RxJavaObservableExecutionHook.implementation"

之後,getPluginImplementationViaProperty函式會通過這個key,從System.property中尋找具體的實現類,然後通過反射構建出具體的實現物件。

//code getPluginImplementationViaProperty()
{
...
String implementingClass = props.getProperty(defaultKey);
 try {
                Class<?> cls = Class.forName(implementingClass);
                // narrow the scope (cast) to the type we're expecting
                cls = cls.asSubclass(pluginClass);
                return cls.newInstance();
            }
...
}複製程式碼

我們不妨來試一下這種寫法,還是基於上面的簡單例子,我們在程式碼前增加一段話:

{
System.setProperty("rxjava.plugin.RxJavaObservableExecutionHook.implementation", 
                "demos.rx.RxJavaObservableExecutionHookImpl");
//配置hook實現類
Observable.just("str1", "str2", "str3", "str4")...
}複製程式碼

RxJavaObservableExecutionHookImpl是我們實現的一個RxJavaObservableExecutionHook型別:

public class RxJavaObservableExecutionHookImpl extends  RxJavaObservableExecutionHook{

    @Override
    public <T> OnSubscribe<T> onCreate(OnSubscribe<T> f) {
        System.out.println("perform intercept onCreate");
        return super.onCreate(f);
    }
}複製程式碼

我們執行以下輸出:

output:
perform intercept onCreate //被我們的hook函式攔截
perform intercept onCreate //被我們的hook函式攔截
onNext [str1]
onNext [str2]
onNext [str3]
onNext [str4]
onCompleted

我們可以從輸出日誌看出,我們所配置的hook類,確實被構造,並且成功實現了hook操作。根據上面所述,如果我們不採用配置Hook類的方式,RxJava將呼叫一個預設的實現類:RxJavaObservableExecutionHookDefault.getInstance()。而這個類的主要操作實際上就是直接返回,不進行任何的攔截:

//code RxJavaObservableExecutionHookDefault
public <T> OnSubscribe<T> onCreate(OnSubscribe<T> f) {
        return f;//直接返回,不進行任何攔截和轉換
    }複製程式碼

好的,我們花了很大的篇幅就是講了RxJavaHooks的onCreate函式,我們在沒有配置任何的hook函式的情況下,返回值就是我們所傳入的OnSubscribe物件。那麼什麼是OnSubscribe物件呢?我們先來看下OnSubscribe這個類吧:

public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {
        // cover for generics insanity
    }
public interface Action1<T> extends Action {
    void call(T t);
}複製程式碼

OnSubscribe繼承於Action1,OnSubscribe限定了在Action1宣告中的泛型變數T,是一個Subscriber型別,而T變數宣告應用在Action1的call函式中,所以,OnSubscribe實際上是限定了OnSubscribe中的call方法的引數型別是一個Subscriber型別。但這並沒有解釋OnSubscribe是個什麼東西,我們來看下OnSubscribe的繼承樹:

OnSubscribe的繼承樹
OnSubscribe的繼承樹

在OnSubscribe型別的頂端是一個Function。Function就是一個函式或者說一個過程,那麼OnSubscribe是一個什麼樣的過程呢?OnSubscribe是一個當訂閱者訂閱的時候,執行的一個過程。正如OnSubscribe這個類名所描述的那樣,這個過程的觸發在Subscribe的時候。這實際上是一種策略的模式,根據不同的需求構建不同的過程策略,比如我們回到上面說的例子中,當我們傳入一個陣列物件的時候:

public static <T> Observable<T> from(T[] array) {
        ....
        return unsafeCreate(new OnSubscribeFromArray<T>(array));
    }複製程式碼

RxJava將採用一個叫做OnSubscribeFromArray的策略物件傳遞給unsafeCreate函式。為了繼續說明這點我們不妨在來看下map函式:

public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
        return unsafeCreate(new OnSubscribeMap<T, R>(this, func));
    }複製程式碼

正如我上面所說的一樣,在map函式中,基於我們上次構造的Observable物件又生成了一個新的Observable物件,而新生成的物件,將採用OnSubscribeMap策略來處理訂閱事件。這種包裝的寫法實際上是一種職責鏈模式。回顧一下我們上面簡單例子的那個流程:
1.通過just生成一個陣列Observable物件-Observable1
2.通過map完成對映,在Observable1之上包裝,生成一個新的Observable物件Observable2
3.通過subscribe函式訂閱Observable2物件

引用關係圖
引用關係圖

通過上面的"引用關係圖"我們可以很清楚的看到Observable型別的整條職責鏈,那麼當我們呼叫Observable.subscribe的時候發生了什麼呢?

public final Subscription subscribe(Subscriber<? super T> subscriber) {
        return Observable.subscribe(subscriber, this);
    }複製程式碼

這個方法中呼叫了一個靜態方法subscribe(subscriber, this)來生成這種訂閱關係。

static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {
        ....pre check
        subscriber.onStart();
        ....
        try {
            // allow the hook to intercept and/or decorate
            RxJavaHooks.onObservableStart(observable, observable.onSubscribe).call(subscriber);
            return RxJavaHooks.onObservableReturn(subscriber);
        } catch (Throwable e) {
              ....
            }
            return Subscriptions.unsubscribed();
        }
    }複製程式碼

這個函式中,會:
1.進行pre check檢查引數是否合法
2.回撥subscriber.onStart方法,告訴訂閱者我這邊已經準備開始了
3.之後就是我們的老朋友RxJavaHooks物件執行onObservableStart,用來在onSubscribe函式執行前做一次hook。(如何hook根據我們上面的方法可以實現,不再贅述)
4.通過呼叫onSubscribe物件的call方法執行函式操作
5.通過RxJavaHooks的onObservableReturn去hook訂閱操作執行結束以後的返回值

根據我們上面的"引用關係圖",我們可以知道訂閱者發生訂閱的時候,最初執行的onSubscribe物件是OnSubscribeMap型別,我們來看下這個型別的實現:

//code OnSubscribeMap.java

public OnSubscribeMap(Observable<T> source, Func1<? super T, ? extends R> transformer) {
        this.source = source;
        this.transformer = transformer;
    }
@Override
    public void call(final Subscriber<? super R> o) {
        MapSubscriber<T, R> parent = new MapSubscriber<T, R>(o, transformer);
        o.add(parent);
        source.unsafeSubscribe(parent);
    }複製程式碼

OnSubscribeMap在構造的時候需要傳遞兩個引數
1.輸入源Observable物件source
2.對映函式:transformer

當呼叫call方法的時候OnSubscribeMap會生成一個新的訂閱物件MapSubscriber,然後註冊到source物件(對應例子中的Observable1)的訂閱者中。unsafeSubscribe執行程式碼:

public final Subscription unsafeSubscribe(Subscriber<? super T> subscriber) {
        try {
            subscriber.onStart();
            RxJavaHooks.onObservableStart(this, onSubscribe).call(subscriber);
            return RxJavaHooks.onObservableReturn(subscriber);
}複製程式碼

這時候unsafeSubscribe中的訂閱Observable的onSubscribe函式物件就是"引用關係圖"中的Observable1.OnSubscribeFromArray物件。一樣,我們看下OnSubscribeFromArray的call方法:

 @Override
    public void call(Subscriber<? super T> child) {
        child.setProducer(new FromArrayProducer<T>(child, array));
    }複製程式碼

這裡我們引入了一個新的類Producer,而OnSubscribeFromArray中所引用的實現類是FromArrayProducer。我們根據上面的呼叫鏈可以知道此時,傳入OnSubscribeFromArray.call方法中的引數child物件,對應著已經被OnSubscribeMap裝飾過的MapSubscriber物件。而在OnSubscribeFromArray.call方法中呼叫了Subscriber的setProducer方法,我們看下這個方法是幹什麼的:

public void setProducer(Producer p) {
        long toRequest;
        boolean passToSubscriber = false;
        synchronized (this) {
            toRequest = requested;
            producer = p;
            if (subscriber != null) {
                if (toRequest == NOT_SET) {
                    passToSubscriber = true;
                }
            }
        }
        // do after releasing lock
        if (passToSubscriber) {
            subscriber.setProducer(producer);
        } else {
            if (toRequest == NOT_SET) {
                producer.request(Long.MAX_VALUE);
            } else {
                producer.request(toRequest);
            }
        }
    }複製程式碼

Producer顧名思義,就是對一個生產者的一個抽象,而生產什麼東西呢?生產的是資料,Producer.request(int n)函式中的n引數代表讓生產者生產多少的資料物件。為什麼需要這個方法呢?toRequest變數又是從何而來呢?toRequest由Subscriber的成員變數requested,而requested通過Subscriber的request函式進行賦值:

protected final void request(long n) {
        if (n < 0) {
            throw new IllegalArgumentException("number requested cannot be negative: " + n);
        }
        Producer producerToRequestFrom;
        synchronized (this) {
            if (producer != null) {
                producerToRequestFrom = producer;
            } else {
                addToRequested(n);//如果沒有producer需要計數
                return;
            }
        }
        // after releasing lock (we should not make requests holding a lock)
        producerToRequestFrom.request(n);
    }複製程式碼

程式碼寫的很清楚,當你的這個訂閱者物件Subscriber並沒有對應的producer的時候,每一次請求資料的操作都會被記錄到你的requested變數中,這樣,當你進行設定了producer的時候,就可以知道自己請求了多少次,需要多少個資料物件。那麼我們回到Subscriber的setProducer方法中去,當程式碼執行到最後,Subscriber會呼叫Producer的request方法來請求資料,而這裡所對應的Producer物件,就是在OnSubscribeFromArray.call方法中傳遞進來的FromArrayProducer型別物件。

public void call(Subscriber<? super T> child) {
        child.setProducer(new FromArrayProducer<T>(child, array));
    }複製程式碼

根據我們最終輸出的日誌,我們可以推測FromArrayProducer是進行了一次陣列的迭代遍歷,那麼是不是這樣呢?我們看下FromArrayProducer的request方法:

@Override
        public void request(long n) {
            if (n < 0) {//異常引數檢查
                throw new IllegalArgumentException("n >= 0 required but it was " + n);
            }
            if (n == Long.MAX_VALUE) {
                if (BackpressureUtils.getAndAddRequest(this, n) == 0) {
                    fastPath();
                }
            } else
            if (n != 0) {
                if (BackpressureUtils.getAndAddRequest(this, n) == 0) {
                    slowPath(n);
                }
            }
        }複製程式碼

訂閱函式呼叫時序圖
訂閱函式呼叫時序圖

這裡,在FromArrayProducer的request處理的時候執行了兩個分支分別對應執行fastPath方法和slowPath方法。而在執行之前有判斷了一個條件:

BackpressureUtils.getAndAddRequest(this, n)
//code BackpressureUtils.getAndAddRequest()
public static long getAndAddRequest(AtomicLong requested, long n) {
        // add n to field but check for overflow
        while (true) {
            long current = requested.get();
            long next = addCap(current, n);
            if (requested.compareAndSet(current, next)) {
                return current;
            }
        }
    }複製程式碼

注意這裡的傳遞物件:
1.requested引數對應的是FromArrayProducer物件
2.n對應的就是我們所傳遞的請求總數
requested初始值為0,通過addCap將數值加入到requested物件中,這樣就完成了生成物件的統計操作。

public static long addCap(long a, long b) {
        long u = a + b;
        if (u < 0L) {//防止越界
            u = Long.MAX_VALUE;
        }
        return u;
    }複製程式碼

同時我們也可以看到,由於產生請求以後,FromArrayProducer統計數增加,因此返回的(BackpressureUtils.getAndAddRequest(this, n) 必不為0。所以每一個資料生產者FromArrayProducer物件只能被使用一次,這時候有人會問了如果我用以下的程式碼,資料可以被回撥兩次的。

Observable<String> ob = Observable.just("str1", "str2", "str3", "str4");
        ob.subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                System.out.println("onCompleted ");
            }
            @Override
            public void onNext(String t) {
                System.out.println("onNext " + t);
            }

            @Override
            public void onError(Throwable e) {}
        });
        System.out.println("----------");
        ob.subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                System.out.println("onCompleted ");
            }
            @Override
            public void onNext(String t) {
                System.out.println("onNext " + t);
            }

            @Override
            public void onError(Throwable e) {}
        });複製程式碼

最後輸出:

output:
//第一次輸出
onNext str1
onNext str2
onNext str3
onNext str4
onCompleted
onNext str1//第二次輸出
onNext str2
onNext str3
onNext str4
onCompleted

這是為什麼呢?
回答這個問題,我們需要回到我們的OnSubscribeFromArray類中:

@Override
    public void call(Subscriber<? super T> child) {
        child.setProducer(new FromArrayProducer<T>(child, array));
    }複製程式碼

因為每發生一次訂閱操作就生成一個全新的FromArrayProducer物件,因此你用到資料自然是在的。我們最後再來看下fastPath函式和slowPath函式,字面意思上好像是代表快慢的路徑搜尋,我們現在看下fastPath函式:

void fastPath() {
            final Subscriber<? super T> child = this.child;
            for (T t : array) {
                if (child.isUnsubscribed()) {
                    return;
                }
                child.onNext(t);
            }
            if (child.isUnsubscribed()) {
                return;
            }
            child.onCompleted();
        }複製程式碼

程式碼非常簡單,就是遍歷記憶體中的陣列array,然後執行訂閱者的onNext回撥和onCompleted回撥函式。而slowPath方法呢?

void slowPath(long r) {
            final Subscriber<? super T> child = this.child;
            final T[] array = this.array;
            final int n = array.length;
            long e = 0L;
            int i = index;//當前資料流索引
            for (;;) {
                while (r != 0L && i != n) {
                    child.onNext(array[i]);
                    i++;
                    if (i == n) {
                        return;
                    }
                    r--;
                    e--;
                }
                r = get() + e;
                if (r == 0L) {
                    index = i;
                    r = addAndGet(e);
                    if (r == 0L) {
                        return;
                    }
                    e = 0L;
                }
            }
        }複製程式碼

這個函式中幾個重要的引數:
1.引數r代表你的請求數
2.e代表資料消耗數量
3.n代表你的陣列長度
4.index代表你的陣列資料流索引
這個函式執行的時候會先執行一個大迴圈,而這個大迴圈中包含著一個小迴圈:

while (r != 0L && i != n) {
                    child.onNext(array[i]);
                    i++;
                    if (i == n) {
                        return;
                    }
                    r--;
                    e--;
                }複製程式碼

用來判斷資料流i是否結束,或者請求數r是否滿足。當滿足一個條件以後跳出迴圈執行addAndGet方法將請求數目加入到計數器中:

 r = get() + e;
                if (r == 0L) {
                    index = i;
                    r = addAndGet(e);
                    if (r == 0L) {
                        return;
                    }
                    e = 0L;
                }複製程式碼

按照"訂閱函式呼叫時序圖"我們知道,此時我們的訂閱者類是被Observable.map函式裝飾過的MapSubscriber類,前面我們說過,這個類是一個Subscriber類的裝飾器,我們來看下它的基本實現:

 public MapSubscriber(Subscriber<? super R> actual, Func1<? super T, ? extends R> mapper) {
            this.actual = actual;
            this.mapper = mapper;
        }複製程式碼

從構造器上看,在構建MapSubscriber類的時候需要指定它的被裝飾物件和對映函式mapper,而當我們回撥到MapSubscriber的onNext回撥的時候:

@Override
        public void onNext(T t) {
            R result;
            try {
                result = mapper.call(t);//對映轉換
            } catch (Throwable ex) {
                ....
                return;
            }
            actual.onNext(result);//回撥被裝飾物件
        }複製程式碼

我們傳入的原始資料t,將被mapper對映函式處理,轉化為一個R型別的結果result,然後把這個結果回撥給被裝飾物件。按照我們上面的例子,這裡面我們的對映函式就是將t外面增加"[]"的Func1介面函式,actual被裝飾物件就是我們程式碼中的匿名訂閱者物件。

好了我們總結一下,我們通過上面一個非常非常簡單例子我們接觸到RxJava這個大家族中的很多核心類:
1.Observable是一個被觀察者物件,每個訂閱者需要通過subscribe方法與Observable物件簽訂訂閱契約
2.Observable的構建是一系列OnSubscribe物件職責鏈式處理過程
3.RxJava中可以在觀察的每個階段配置hook函式
4.鏈式處理過程中,訂閱者Subscriber物件可能會被鏈條中的中間環節所包裝
5.Producer是用來定義生產資料的型別
6.Subscriber在函式setProducer中呼叫Producer的request(int n)方法用於請求n個資料

相關文章