RxJava2如何實現鏈式調用和線程切換

線程切換

背壓

背壓在計算機系統中指網絡擁塞信息逆流通過。在rxjava中理解爲:被觀察者發送消息太快以至於它的操作符或者訂閱者不能及時處理相關的消息,從而操作消息的阻塞現象

在RxJava2.0中官方,推出了Flowable 和Subscriber用來支持背壓,同樣的去除了Observable對背壓的支持,對的就像你上面看到的,Observable不再支持背壓了,即使阻塞崩潰也不會拋出MissingBackpressureException

Flowable的用法吧。

Flowable.create(FlowableOnSubscribe<T> source, BackpressureStrategy mode)

FlowableOnSubscribe很好理解就是一個就是Flowable的一個被觀察者源,而BackpressureStrategy就是Flowable提供的背壓策略

背壓策略

  1. MISSING:
    如果流的速度無法保持同步,可能會拋出MissingBackpressureException或IllegalStateException。
  2. BUFFER
    上游不斷的發出onNext請求,直到下游處理完,也就是和Observable一樣了,緩存池無限大,最後直到程序崩潰
  3. ERROR
    會在下游跟不上速度時拋出MissingBackpressureException。
  4. DROP
    會在下游跟不上速度時把onNext的值丟棄。
  5. LATEST
    會一直保留最新的onNext的值,直到被下游消費掉。

鏈式調用

變換/操作符
  1. 所謂變換,就是將事件序列中的對象或整個序列進行加工處理,轉換成不同的事件或事件序列。比如要修改Observable對象,把字符串改爲整型或者加上另一串字符等等。
  2. 操作符就是爲了解決對Observable對象的變換的問題,操作符用於在Observable和最終的Subscriber之間修改Observable發出的事件。
  3. 變換操作符API
  • from()
    Observable.from()方法,它接收一個集合作爲輸入,然後每次輸出一個元素給subscriber
Observable.from("url1", "url2", "url3")
.subscribe(new Action1<String>() { 
        @Override  
        public void call(String s) { 
            System.out.println(s); 
        }  
    });  
  • map()改變observable對象發出的數據類型
    map操作符有趣的一點是它不必返回Observable對象返回的類型,我們可以使用map操作符返回一個發出新的數據類型的observable對象。
    比如上面的例子中,subscriber如果並不關心返回的字符串,而是想要字符串的hash值,則可以這樣寫:
Observable.just("Hello, world!")    //輸入類型 String
    .map(new Func1<String, Integer>() {  //String ---> Integer
        @Override  
        public Integer call(String s) {  //參數類型 String
            return s.hashCode();        //返回類型 Integer
        }  
    })  
    .subscribe(new Action1<Integer>() {  //由map()返回的Observable對象調用
        @Override  
        public void call(Integer i) {   //參數類型 Integer(即上面的返回類型)
              System.out.println(Integer.toString(i));  
        }  
    });  

這裏出現了一個叫做 Func1 的類。它和 Action1 非常相似,也是 RxJava 的一個接口,用於包裝含有一個參數的方法。 Func1 和 Action 的區別在於,** Func1 包裝的是有返回值的方法**。另外,和 ActionX 一樣, FuncX 也有多個,用於不同參數個數的方法。(用於輸入與輸出參數數據類型的變換)

  • flatMap()
    flatMap() 和 map() 有一個相同點:它也是把傳入的參數轉化之後返回另一個對象。但需要注意,和 map() 不同的是, flatMap() 中返回的是個 Observable 對象,並且這個 Observable 對象並不是被直接發送到了 Subscriber 的回調方法中map()解決的是一對一的變換,flatMap()解決的是一對多的變換。

flatMap()的原理

  1. 使用傳入的事件對象創建一個 Observable 對象;
  2. 並不發送這個 Observable, 而是將它激活,於是它開始發送事件;
  3. 每一個創建出來的 Observable 發送的事件,都被匯入同一個 Observable,而這個 Observable 負責將這些事件統一交給Subscriber 的回調方法。
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
    @Override
    public void onNext(Course course) {
        Log.d(tag, course.getName());       //打印課程名
    }
    ...
};
Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>() {
        @Override
        public Observable<Course> call(Student student) {     //輸入參數 student
            return Observable.from(student.getCourses());        //返回student對應課程 Observable
        }
    })
    .subscribe(subscriber);

以上例程通過flatMap() 實現打印每一個student的課程(一個student對應多個課程)。

變換的原理 lift()

這些變換雖然功能各有不同,但實質上都是針對事件序列的處理和再發送。而在 RxJava 的內部,它們是基於同一個基礎的變換方法: lift(Operator)。

在 Observable 執行了 lift(Operator) 方法之後,會返回一個新的Observable,這個新的 Observable 會像一個代理一樣,負責接收原始的 Observable 發出的事件,並在處理後發送給 Subscriber。不建議自定義Operater()來直接使用lift()

  • 圖解

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Eaaf06jE-1588413213185)(http://note.youdao.com/yws/res/3668/5C228296E2CC48A7A789386F849B0DD6)]

  • lift() 創建了一個 Observable 後,加上之前的原始 Observable,已經有兩個 Observable 了;
  • 而同樣地,新 Observable 裏的新 OnSubscribe 加上之前的原始 Observable 中的原始 OnSubscribe,也就有了兩個 OnSubscribe;
  • 當用戶調用經過 lift() 後的 Observable 的 subscribe() 的時候,使用的是 lift() 所返回的新的 Observable ,於是它所觸發的 onSubscribe.call(subscriber),也是用的新 Observable 中的新 OnSubscribe,即在 lift() 中生成的那個 OnSubscribe;
  • 而這個新 OnSubscribe 的 call() 方法中的 onSubscribe ,就是指的原始 Observable 中的原始 OnSubscribe ,在這個 call() 方法裏,新 OnSubscribe 利用 operator.call(subscriber) 生成了一個新的 Subscriber(Operator 就是在這裏,通過自己的 call() 方法將新 Subscriber 和原始 Subscriber 進行關聯,並插入自己的『變換』代碼以實現變換),然後利用這個新 Subscriber 向原始 Observable 進行訂閱。
    這樣就實現了 lift() 過程,有點像一種代理機制,通過事件攔截和處理實現事件序列的變換。
  1. compose: 對 Observable 整體的變換
  • 它和 lift() 的區別在於,lift() 是針對事件項和事件序列的,而 compose() 是針對 Observable 自身進行變換。
  • 假設在程序中有多個 Observable ,並且他們都需要應用一組相同的 lift() 變換。可以這麼寫:
public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
    @Override
    public Observable<String> call(Observable<Integer> observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

線程切換

在 RxJava 的默認規則中,事件的發出和消費都是在同一個線程的。也就是說,如果只用上面的方法,實現出來的只是一個同步的觀察者模式。觀察者模式本身的目的就是『後臺處理,前臺回調』的異步機制,因此異步對於 RxJava 是至關重要的。
在不指定線程的情況下, RxJava 遵循的是線程不變的原則,即:在哪個線程調用 subscribe(),就在哪個線程生產事件;在哪個線程生產事件,就在哪個線程消費事件.如果需要切換線程,就需要用到 Scheduler (調度器)

  1. Schedule的API
  • Schedule的幾個線程調用函數(5個):
  • Schedulers.immediate(): 直接在當前線程運行,相當於不指定線程。這是默認的schedule。
  • Schedulers.newThread(): 總是啓用新線程,並在新線程執行操作。
  • Schedulers.io(): I/O 操作(讀寫文件、讀寫數據庫、網絡信息交互等)所使用的 Scheduler。行爲模式和newThread() 差不多,區別在於 io() 的內部實現是是用一個無數量上限的線程池,可以重用空閒的線程,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免創建不必要的線程。
  • Schedulers.computation(): 計算所使用的 Scheduler。這個計算指的是 CPU 密集型計算,即不會被 I/O 等操作限制性能的操作,例如圖形的計算。這個 Scheduler 使用的固定的線程池,大小爲 CPU 核數.不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。
  • AndroidSchedulers.mainThread(),Android 專用的,它指定的操作將在 Android 主線程運行。

有了這幾個 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 兩個方法來對線程進行控制了

  • subscribeOn(): 指定 subscribe() 所發生的線程,即 Observable.OnSubscribe 被激活時所處的線程。或者叫做事件產生的線程。
  • observeOn(): 指定 Subscriber 所運行在的線程。或者叫做事件消費的線程。即做出響應的線程。

例程:

Observable.just(1, 2, 3, 4).subscribeOn(Schedulers.io())     // 指定 subscribe() 發生在 IO 線程
.observeOn(AndroidSchedulers.mainThread())       // 指定 Subscriber 的回調發生在主線程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);   //發生在主線程
}
});

線程控制(二):Scheduler

  • 上面講到可以利用 subscribeOn() 結合 observeOn() 來實現線程控制,讓事件的產生和消費發生在不同的線程。又知道map(),和flatMap()等可以做不同的變換,那麼也可以通過多次地調用函數來實現多次線程的切換
Observable.just(1, 2, 3, 4) // IO 線程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新線程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 線程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主線程,由 observeOn() 指定

通過 observeOn() 的多次調用,程序實現了線程的多次切換。不過,不同於 observeOn() , subscribeOn() 的位置放在哪裏都可以,但它是隻能調用一次的。(因爲只有第一次的SubscribeOn()是有效的)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lm8WtUkM-1588413213189)(http://note.youdao.com/yws/res/7236/C7106F0289D24649B6F9F2273A7F14FE)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uFiRMedA-1588413213197)(http://note.youdao.com/yws/res/7238/CECF9714431E4E8B9DEB58F1028FE997)]

  • subscribeOn() 和 observeOn() 都做了線程切換的工作(圖中的 “schedule…” 部位)。不同的是, subscribeOn() 的線程切換髮生在 OnSubscribe 中,即在它通知上一級 OnSubscribe 時,這時事件還沒有開始發送,因此 subscribeOn() 的線程控制可以從事件發出的開端就造成影響;而 observeOn() 的線程切換則發生在它內建的 Subscriber 中,即發生在它即將給下一級 Subscriber 發送事件時,因此 observeOn()控制的是它後面的線程。

  • 上游事件是怎麼給弄到子線程裏去的?
    就是直接把訂閱方法放在了一個Runnable中去執行,這樣就一旦這個Runnable在某個子線程執行,那麼上游所有事件只能在這個子線程中執行了。

  • 線程間傳遞消息也是使用 Handler來實現。

延伸:doOnSubscribe()
雖然超過一個的 subscribeOn() 對事件處理的流程沒有影響,但在流程之前卻是可以利用的。
onStart() 由於在 subscribe() 發生時就被調用了,因此不能指定線程,而是隻能執行在 subscribe() 被調用時的線程。這就導致如果 onStart() 中含有對線程有要求的代碼(例如在界面上顯示一個 ProgressBar,這必須在主線程執行),將會有線程非法的風險,因爲有時你無法預測 subscribe() 將會在什麼線程執行。而與 Subscriber.onStart() 相對應的方法 Observable.doOnSubscribe() 。它和Subscriber.onStart() 同樣是在 subscribe() 調用後而且在事件發送前執行,但區別在於它可以指定線程。默認情況下, doOnSubscribe() 執行在 subscribe() 發生的線程;而如果在 doOnSubscribe() 之後有subscribeOn() 的話,它將執行在離它最近的 subscribeOn() 所指定的線程。

Observable.create(onSubscribe)
    .subscribeOn(Schedulers.io())    //事件的發出還是在io線程
 
    .doOnSubscribe(new Action0() {
        @Override
        public void call() {
            progressBar.setVisibility(View.VISIBLE); // 需要在主線程執行
        }
    })
    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主線程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);

subscribeOn() 的線程切換原理

@CheckReturnValue
    @SchedulerSupport(SchedulerSupport.CUSTOM)
    public final Observable<T> subscribeOn(Scheduler scheduler) {
        //非空判斷,若爲空會直接拋出異常
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        //這裏將Observable和Scheduler封裝成了ObservableSubscribeOn
        //onAssembly方法返回了這個new ObservableSubscribeOn<T>(this, scheduler)對象
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
    }
  • 首先subscribeOn方法中將Observable和Scheduler封裝成了ObservableSubscribeOn(是Observable的子類)。返回了一個新的Observable,而這個新的Observable裏面持有一個上一層Observable的引用

  • 接着,訂閱的時候調用subscribe方法,在subscribe方法中會調用封裝的ObservableSubscribeOn的subscribeActual()方法(方法裏面會先調用observer的onSubscribe()方法,此時的Observable的subscribe方法發生在當前線程,所以Observer的onSubscribe方法的執行線程和當前調用Observable的subscribe方法的線程一致!!!

public void subscribeActual(Observer<? super T> s) {
    ObservableSubscribeOn.SubscribeOnObserver<T> parent = new ObservableSubscribeOn.SubscribeOnObserver(s);
    s.onSubscribe(parent);
    parent.setDisposable(this.scheduler.scheduleDirect(new ObservableSubscribeOn.SubscribeTask(parent)));
}

  • 裏面new了一個SubscribeTask, ObservableSubscribeOn.SubscribeTask(parent)(ObservableSubscribeOn.SubscribeTask是一個Runnable),實現了Runnable接口,它的run方法會在subscribeOn指定的Scheduler的線程中執行。當我們的 SubscribeTask 的 run 方法運行在哪個線程,相應的 observable 的 subscribe 方法就運行在哪個線程。

  • SubscribeTask 包裝 parent(SubscribeOnObserver ,包裝了 Observer),SubscribeTask 實現了 Runnable 接口,在 run 方法裏面調用了 source.subscribe(parent),因而 run 方法所執行的線程將由 worker 決定。這就是 下游決定上游 observable 執行線程的原理

observeOn() 的線程切換原理

@CheckReturnValue
@SchedulerSupport("custom")
public final Observable<T> observeOn(Scheduler scheduler) {
    return this.observeOn(scheduler, false, bufferSize());
}
@CheckReturnValue
@SchedulerSupport("custom")
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    ObjectHelper.verifyPositive(bufferSize, "bufferSize");
    return RxJavaPlugins.onAssembly(new ObservableObserveOn(this, scheduler, delayError, bufferSize));
}

同樣地

  • 首先observeOn方法中將Observable和Scheduler封裝成了ObservableObserveOn(是Observable的子類)。返回了一個新的Observable,而這個新的Observable裏面持有一個上一層Observable的引用
  • 接着,訂閱的時候調用subscribe方法,在subscribe方法中會調用封裝的ObservableObserveOn的subscribeActual()方法
protected void subscribeActual(Observer<? super T> observer) {
        if (this.scheduler instanceof TrampolineScheduler) {
            this.source.subscribe(observer);
        } else {
            Worker w = this.scheduler.createWorker();
            this.source.subscribe(new ObservableObserveOn.ObserveOnObserver(observer, w, this.delayError, this.bufferSize));
        }

    }
  • 裏面new了一個ObserveOnObserver對象,它的onNext、onComplete、onError方法裏面都調用了schedule方法。
void schedule() {
        if (this.getAndIncrement() == 0) {
            this.worker.schedule(this);
        }

}
  • 執行worker的schedule方法,其中worker是我們調用observeOn(Schedule)方法時傳入的Schedule的Worker,比如IoSchedule的ThreadWorker,最終,worker會執行其scheduleActual方法。最後無非是用線程池提供一個線程,執行ObservableObserveOn.ObserveOnObserver的run方法,ObservableObserveOn.ObserveOnObserver是實現了Runnable接口的。

總結:

  1. 子線程切換主線程:給主線程所在的Handler發消息,然後就把邏輯切換過去了。

  2. 主線程切換子線程:把任務放到線程池中執行就能把執行邏輯切換到子線程

  3. 子線程切換子線程:把任務分別扔進兩個線程就行了。

爲什麼 subscribeOn() 只有第一次切換有效

因爲 RxJava 最終能影響 ObservableOnSubscribe 這個匿名實現接口的運行環境的只能是最後一次運行的 subscribeOn() ,又因爲 RxJava 訂閱的時候是從下往上訂閱,所以從上往下第一個 subscribeOn() 就是最後運行的,這就造成了寫多個 subscribeOn() 只有第一個subscribeOn()有用。

https://www.jianshu.com/p/88aacbed8aa5

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-o0Ah7Wmc-1588413213205)(http://note.youdao.com/yws/res/9208/56E8E1BF2E2045248C066C027497EF60)]

subscribeOn()產生的線程切換髮生在代碼執行的第二層,而它的回溯又將會執行在新的線程中。因此,在subscribeOn切換線程以後的流程,均將在新的線程中執行。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ls5mMUr2-1588413213214)(https://note.youdao.com/src/09B1E92C38784C9E9FC41CCC8A5AB080)]

observeOn()產生的線程切換都發生在第三層執行層,而在切換線程前的業務代碼由於已經執行了,故不受切換線程切換的影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章