RxJava作為目前一款超火的框架,它便捷的執行緒切換一直被人們津津樂道,本文從原始碼的角度,來對RxJava的執行緒模型做一次深入理解。(注:本文的多處程式碼都並非原本的RxJava的原始碼,而是用來說明邏輯的虛擬碼)
入手體驗
RxJava 中切換執行緒非常簡單,例如最常見的非同步執行緒處理,主執行緒回撥的模型,可以很優雅的用如下程式碼來做處理:
1 2 3 4 5 |
Observable.just("magic") .map(str -> doExpensiveWork(str)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(obj -> System.out.print(String.valueOf(obj))); |
如上,subscribeOn(Schedulers.io())
保證了doExpensiveWork
函式發生在io執行緒,observeOn(AndroidSchedulers.mainThread())
保證了subscribe
回撥發生在Android 的主執行緒。所以,這自然而然的引出了本文的關鍵點,subscribeOn
與observeOn
到底區別在哪裡?
流程淺析
要想回答上面的問題,我們首先需要對RxJava的流程有大體瞭解,一個Observable從產生,到最終執行subscribe,中間可以經歷n個變換,每次變換會產生一個新的Observable,就像奧運開幕的傳遞火炬一樣,每次火炬都會傳遞到下一個人,最終點燃聖火的是最後一個火炬手,即最終執行subscribe操作的是最後一個Observable,所以,每個Observable之間必須有聯絡,這種關係在程式碼中的體現就是,每個變換後的Observable都會持有上一個Observable 中OnSubscribe物件的引用(Observable.create 函式所需的引數),最終 Observable的subscribe函式中的關鍵程式碼是這一句:
1 |
observable.onSubscribe.call(subscriber) |
這個observable就是最後一個變換後的observable,那這個onSubscribe物件是誰呢?如何一個observable沒有經過任何變換,直接執行了subscribe,當然就是我們在create中傳入的onSubscribe, 但如果中間經過map、reduce等變換,這個onSubscribe顯然就應該是建立變換後的observable傳入的引數,大部分變換最終都交由lift函式:
1 2 3 |
public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) { return new Observable<R>(new OnSubscribeLift<T, R>(onSubscribe, operator)); } |
所以,上文所提到的onSubscribe物件應該是OnSubscribeLift的例項,而這個OnSubscribeLift所接收的兩個引數,一個是前文提到的,上一個Observable中的OnSubscribe物件,而operator則是每種變換的一個抽象介面。再來看這個OnSubscribeLift物件的call方法:
1 2 3 4 |
public void call(Subscriber<? super R> o) { Subscriber<? super T> st = operator.call(o); parent.call(st); } |
operator與parent就是前文提到的兩個引數,可見,operator介面會擁有call方法,接收一個Subscriber, 並返回一個新的Subscriber物件,而接下來的parent.call(st)
是回撥上一層observable的onSubscribe的call方法,這樣如此繼續,一直到一個onSubscribe截止。這樣我們首先理清了一條線路,就是從最後一個observable的subscribe後,OnSubscribe呼叫的順序是從後向前的。
這就帶來了另外一個疑問,從上面的程式碼可以看到,在執行parent.call(st)
之前已經執行了operator.call(o)
方法,如果call方法裡就把變換的操作執行了的話,那似乎變換也會是從後向前傳遞的呀?所以這個operator.call
方法絕對不是我們想象的那麼簡單。這裡以map操作符為例,看原始碼:
1 2 3 4 5 |
public Subscriber<? super T> call(final Subscriber<? super R> s) { MapSubscriber<T, R> parent = new MapSubscriber<T, R>(o, transformer); o.add(parent); return parent; } |
這裡果然沒有執行變換操作,而是生成一個MapSubscriber物件,這裡需要注意MapSubscriber建構函式的兩個引數,transformer是真正要執行變換的Func1物件,這很好理解,那對於o這個Subscriber是哪一個呢?什麼意思?舉個?:
o1 -> o2 -> subscribe(Subscriber s0);
o1 經過map操作變為o2, o2執行subscribe操作,如果你理解上文可以知道,這段流程的執行順序為s0會首先傳遞給o2, o2的lift操作會將s0轉換為s1傳遞給o1, 那麼在生成o2 這個map操作的 call(final Subscriber<? super R> s)
方法中,s值得是誰呢?是s0還是s1呢?答案應該是s0,也就是它的下一級Subscriber,原因很簡單,call方法中返回的MapSubscriber物件parent才是s1.
所以,我們來看一下MapSubscriber的onNext方法做了什麼呢?
1 2 3 4 5 |
public void onNext(T t) { R result; result = transformer.call(t); s.onNext(result); } |
很明瞭,首先執行變換,然後回撥下一級的onNext函式。
至此,一個observable從初始,到變換,再到subscribe,我們已經對整個流程有了大體瞭解。簡單來講一個o1經過map變為o2,可以理解為o2對o1做了一層hook,會經歷兩次流程,首先是onSubscribe物件的call流程會從o2流向o1,我們簡稱流程a,到達o1後,o1又會出發Subscriber的onNext系列流程,簡稱流程b,流程b才是真正執行變換的流程,其走向是從o1流向o2.理解了這個,我們就可以更近一步的理解RxJava中執行緒的模型了。
tip: 一定要深刻理解流程a與流程b的區別。這對下文理解執行緒切換至關重要。
切換方式
RxJava對執行緒模型的抽象是Scheduler
,這是一個抽象類,包含一個抽象方法:
1 |
public abstract Worker createWorker(); |
這個Worker
是何方神聖呢?它其實是Scheduler的抽象內部類,主要 包含兩個抽象方法:
1 2 3 |
1) public abstract Subscription schedule(Action0 action); 2) public abstract Subscription schedule(final Action0 action, final long delayTime, final TimeUnit unit); |
可見,Worker才是執行緒執行的主力,兩個方法一個用與立即執行任務,另一個用與執行延時任務。而Scheduler是Worker的工廠,用於對外提供Worker。
RxJava中共有兩種常見的方式來切換執行緒,分別是subscribeOn變換與observeOn變換,這兩者接收的引數都是Scheduler。接下來從原始碼層面來對比這兩者的差別。
subscribeOn
首先看subscribeOn的部分
1 2 3 |
public final Observable<T> subscribeOn(Scheduler scheduler) { return create(new OperatorSubscribeOn<T>(this, scheduler)); } |
create一個新的Observable,傳入的引數是OperatorSubscribeOn,很明顯這應該是OnSubscribe的一個實現,關注這個OperatorSubscribeOn的call實現方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public void call(final Subscriber<? super T> subscriber) { final Worker inner = scheduler.createWorker(); inner.schedule(new Action0() { @Override public void call() { final Thread t = Thread.currentThread(); Subscriber<T> s = new Subscriber<T>(subscriber) { @Override public void onNext(T t) { subscriber.onNext(t); } ... }; source.unsafeSubscribe(s); } }); } |
這裡比較關鍵了,上文提到了流程a與流程b,首先明確一點,這個call方法的執行時機是流程a,也就是說這個call發生在流程b之前,call方法裡首先通過外部傳入的scheduler建立Worker – inner物件,接著在inner中執行了一段程式碼,神奇了,Action0中call方法這段程式碼就在worker執行緒中執行了,也就是此刻程進行了切換。注意最後一句程式碼source.unsafeSubscribe(s)
,source 代表建立OperatorSubscribeOn物件是傳進來的上一個Observable, 這句的原始碼如下:
1 2 3 |
public final Subscription unsafeSubscribe(Subscriber<? super T> subscriber) { return onSubscribe.call(subscriber); } |
和上文提到的lift方法中OnSubscribeLift物件的call方法中parent.call(st)
作用類似,就是將當前的Observable與上一個Observable通過onSubscribe關聯起來。
至此,我們可以大致瞭解了subscribeOn的原理,它會在流程a就進行了執行緒切換,但由於流程a上實際上都是Observable之間串聯關係的程式碼,並且是從後面的Observable流向前面的Observable,這帶來的一個隱含意思就是,對於流程b而言,最早的subscribeOn會遮蔽其後面的subscribeOn! 比如:
1 2 3 4 5 |
Observable.just("magic") .map(file -> doExpensiveWork(file)) .subscribeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe(obj -> doAction(obj))); |
這段程式碼中無論是doExpensiveWork函式還是doAction函式,都會在io執行緒出觸發。
observeOn
理解了subscribeOn,那理解observeOn就會更容易一下,observeOn函式最終會轉換到這個函式:
1 2 3 |
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) { return lift(new OperatorObserveOn<T>(scheduler, delayError, bufferSize)); } |
很明顯,這是做了一次lift操作,我們需要關注OperatorObserveOn這個Operator,檢視其call方法:
1 2 3 4 5 |
public Subscriber<? super T> call(Subscriber<? super T> child) { ObserveOnSubscriber<T> parent = new ObserveOnSubscriber<T>(scheduler, child, delayError, bufferSize); parent.init(); return parent; } |
這裡返回的是一個ObserveOnSubscriber物件,我們關注這個Subscriber的onNext
函式,
1 2 3 |
public void onNext(final T t) { schedule(); } |
它只是簡單的執行了schedule函式,來看下這個schedule:
1 2 3 |
protected void schedule() { recursiveScheduler.schedule(this); } |
這裡亂入的recursiveScheduler.schedule是什麼鬼?它並不神奇,它就是ObserveOnSubscriber建構函式傳進來的scheduler建立的worker:
1 |
this.recursiveScheduler = scheduler.createWorker(); |
所以,magic再次產生,observeOn在其onNext中進行了執行緒的切換,那這個onNext是在什麼時候執行的呢?通過上文可知,是在流程b中。所以observeOn會影響其後面的流程,直到出現下一次observeOn或者結束。
周邊技巧
執行緒模型的選擇
RxJava為我們內建了幾種執行緒模型,主要區別如下:
- computation內部是一個執行緒,執行緒池的大小cpu核數:
Runtime.getRuntime().availableProcessors()
,這種執行緒比較適合做純cpu運算,如求100億以內的斐波那契數列的和之類。 - newThread每次createWorker都會生成一個新的執行緒。
- io與newThread類似,但內部是一個沒有上線的執行緒池,一般來講,使用io會比newThread好一些,因為其內部的執行緒池可以重用執行緒。
- immediate在當前執行緒立即執行
- trampoline
在當前執行緒執行,與immediate不同的是,它並不會立即執行,而是將其存入佇列,等待當前Scheduler中其它任務執行完畢後執行,這個在我們時常使用的並不多,它主要服務於repeat ,retry這類特殊的變換操作。
- from接收一個Executor,允許我們自定義Scheduler。
Scheduler.Worker強勢搶鏡
其實RxJava中的Worker完全可以抽出來為我所用,如下面這種寫法,就是新開執行緒執行了一個action。
1 2 3 4 5 6 7 |
Scheduler.Worker worker = Schedulers. newThread().createWorker(); worker.schedule(new Action0() { @Override public void call() { throw new RuntimeException("surprise"); } }); |
當然,你要選擇合適的時機去關閉(unsubscribe
)worker來釋放資源。
自帶光環的操作符
某些操作符是有預設的執行緒模型的,比如前文提到的repeat 與retry會預設在trampoline執行緒模型下執行, buffer ,debounce之類會預設切換到computation。這裡不做深入探討,只是當你遇到某些問題時記得,有些人物是自帶裝備與光環的。
總結
理解RxJava的執行緒模型最重要的是要與我們平常對非同步的理解來區分開:
1 2 3 4 5 6 7 8 |
doAsync("magic", new Callback() { @Override public void handle(Object msg) { a) .... } }); b).... |
這是之前我們常寫的程式碼,通常只會區分UI執行緒和非UI 執行緒,doAsync函式開始後,程式進行了分流,一個執行緒在執行一個doAsync, 另一個執行緒在執行b段程式碼。RxJava另闢蹊徑,對整個執行緒做了抽象,RxJava的處理順序像一條流水,這不僅僅表現在程式碼寫起來像一條鎖鏈上,邏輯上也是如此,對Observable自身而言,更改執行緒只是變換了流水前進的軌道,並不是進行分流,Android中常見 非UI執行緒處理資料,UI 執行緒展示資料也只是這條流水變換的一種方式。
就我個人的理解,對於RxJava的執行緒切換,把它理解為非同步,非非同步,阻塞,非阻塞都有些不恰當,暫且只能理解為變換。so amazing!