注:本篇文章程式碼基於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獲取的物件記錄在全域性變數中
這裡引出一個問題,就是我們如何注入一個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型別的頂端是一個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個資料