RxJava 從入門到放棄再到不離不棄

作者寄語

很久之前就想寫一個專題,專寫Android開發框架,專題的名字叫 XXX 從入門到放棄 ,沉淀了這么久,看過網絡諸多大神的博客,靜下心來開始寫這個專題,為什么叫入門到放棄呢;相信大家學習新框架的時候,尤其是像Rxjava或者Dagger等等這種新的編程思想;需要一定的閱讀理解能力和思維邏輯;那么本專題旨在幫助大家不要太過急功近利,不要被冗長的代碼和文章,晦澀的思想所打敗,相信大家只要堅持看完,一定會有所收獲的;廢話不多說,那么這個專題開篇就以RxJava來講吧,預計后面還會有幾篇大型框架的講解,想想還有點小激動;

友情提示:文章較長,請耐心看完;

前言

RxJava等編程思想正在Android開發者中變的越來越流行。唯一的問題就是上手不容易,尤其是大部分人之前都是使用命令式編程語言

首先要先理清這么一個問題:Rxjava和我們平時寫的程序有什么不同。相信稍微對Rxjava有點認知的朋友都會深深感受到用這種方式寫的程序和我們一般寫的程序有很明顯的不同。我們一般寫的程序 統稱為命令式程序,是以流程為核心的,每一行代碼實際上都是機器實際上要執行的指令。而Rxjava這樣的編程風格,稱為函數響應式編程。函數響應式編程是以數據流為核心,處理數據的輸入,處理以及輸出的。這種思路寫出來的代碼就會跟機器實際執行的指令大相徑庭。所以對于已經習慣命令式編程的我們來說,剛開始接觸Rxjava的時候必然會很不適應,而且也不太符合我們平時的思維習慣。但是久而久之你會發現這個框架的精髓,尤其是你運用到大項目中的時候,簡直愛不釋手,隨著程序邏輯變得越來越復雜,它依然能夠保持代碼簡潔。

RxJava是什么

a library for composing asynchronous and event-based programs using observable sequences for the Java VM
解釋:一個對于構成使用的Java虛擬機觀察序列異步和基于事件的程序庫

RxJava 是一個響應式編程框架,采用觀察者設計模式。所以自然少不了 Observable 和 Subscriber 這兩個東東了。
RxJava 是一個開源項目,地址:https://github.com/ReactiveX/RxJava
RxAndroid,用于 Android 開發,添加了 Android 用的接口。地址: https://github.com/ReactiveX/RxAndroid

基本概念

網上關于RxJava的博文也有很多,我也看過許多,其中不乏有優秀的文章,但絕大部分文章都有一個共同點,就是側重于講RxJava中各種強大的操作符,而忽略了最基本的東西——概念,所以一開始我也看的一臉懵逼,看到后面又忘了前面的,腦子里全是問號,這個是什么,那個又是什么,這兩個長得怎么那么像。舉個不太恰當的例子,概念之于初學者,就像食物之于人,當你餓了,你會想吃面包、牛奶,那你為什么不去吃土呢,因為你知道面包牛奶是用來干嘛的,土是用來干嘛的。同理,前面已經說過,RxJava無非是發送數據與接收數據,那么什么是發射源,什么是接收源,這就是你應該明確的事,也是RxJava的入門條件之一,下面就依我個人理解,對發射源和接收源做個歸類,以及RxJava中頻繁出現的幾個“單詞”解釋一通;

  • Observable:發射源,英文釋義“可觀察的”,在觀察者模式中稱為“被觀察者”或“可觀察對象”;

  • Observer:接收源,英文釋義“觀察者”,沒錯!就是觀察者模式中的“觀察者”,可接收ObservableSubject發射的數據;

  • SubjectSubject是一個比較特殊的對象,既可充當發射源,也可充當接收源,為避免初學者被混淆,本章將不對Subject做過多的解釋和使用,重點放在ObservableObserver上,先把最基本方法的使用學會,后面再學其他的都不是什么問題;

  • Subscriber:“訂閱者”,也是接收源,那它跟Observer有什么區別呢?Subscriber實現了Observer接口,比Observer多了一個最重要的方法unsubscribe( ),用來取消訂閱,當你不再想接收數據了,可以調用unsubscribe( )方法停止接收,Observer 在 subscribe() 過程中,最終也會被轉換成 Subscriber 對象,一般情況下,建議使用Subscriber作為接收源;

  • SubscriptionObservable調用subscribe( )方法返回的對象,同樣有unsubscribe( )方法,可以用來取消訂閱事件;

  • Action0RxJava中的一個接口,它只有一個無參call()方法,且無返回值,同樣還有Action1,Action2...Action9等,Action1封裝了含有 1 個參的call()方法,即call(T t),Action2封裝了含有 2 個參數的call方法,即call(T1 t1,T2 t2),以此類推;

  • Func0:與Action0非常相似,也有call()方法,但是它是有返回值的,同樣也有Func0、Func1...Func9;

RxJava最核心的兩個東西是Observables(被觀察者,事件源)和Subscribers(觀察者)。Observables發出一系列事件,Subscribers處理這些事件。這里的事件可以是任何你感興趣的東西(觸摸事件,web接口調用返回的數據...)

一個Observable可以發出零個或者多個事件,知道結束或者出錯。每發出一個事件,就會調用它的SubscriberonNext方法,最后調用Subscriber.onNext()或者Subscriber.onError()結束。

Rxjava的看起來很想設計模式中的觀察者模式,但是有一點明顯不同,那就是如果一個Observerble沒有任何的的Subscriber,那么這個Observable是不會發出任何事件的。

基本用法

Observable的創建

使用create( ),最基本的創建方式:

Observable<String>  myObservable  = Observable.create(new Observable.OnSubscribe<String>() {
  @Override
  public void call(Subscriber<? super String> subscriber) {
      subscriber.onNext("Hello, world!"); //發射一個"Hello, world!"的String
      subscriber.onCompleted();//發射完成,這種方法需要手動調用onCompleted,才會回調Observer的onCompleted方法
  }});

可以看到,這里傳入了一個 OnSubscribe 對象作為參數。OnSubscribe 會被存儲在返回的 Observable 對象中,它的作用相當于一個計劃表,當 Observable 被訂閱的時候,OnSubscribecall() 方法會自動被調用,事件序列就會依照設定依次觸發(對于上面的代碼,就是觀察者Subscriber將會被調用一次 onNext() 和一次 onCompleted())。這樣,由被觀察者調用了觀察者的回調方法,就實現了由被觀察者向觀察者的事件傳遞,即觀察者模式

這個例子很簡單:事件的內容是字符串,而不是一些復雜的對象;事件的內容是已經定好了的,而不像有的觀察者模式一樣是待確定的(例如網絡請求的結果在請求返回之前是未知的);所有事件在一瞬間被全部發送出去,而不是夾雜一些確定或不確定的時間間隔或者經過某種觸發器來觸發的。總之,這個例子看起來毫無實用價值。但這是為了便于說明,實質上只要你想,各種各樣的事件發送規則你都可以自己來寫。至于具體怎么做,后面都會講到,但現在不行。只有把基礎原理先說明白了,上層的運用才能更容易說清楚。


Subscriber的創建

上面定義的Observable對象僅僅發出一個Hello World字符串,然后就結束了。接著我們創建一個Subscriber來處理Observable對象發出的字符串:

Subscriber<String> mySubscriber = new Subscriber<String>() {  
    @Override  
    public void onNext(String s) {
         System.out.println(s); //打印出"Hello, world!"
    }  
  
    @Override  
    public void onCompleted() { }  
  
    @Override  
    public void onError(Throwable e) { }  
};  

除了 Observer 接口之外,RxJava 還內置了一個實現了 Observer 的抽象類:SubscriberSubscriberObserver 接口進行了一些擴展,但他們的基本使用方式是完全一樣的:

Observer<String> myObserver = new Observer<String>() {  
    @Override  
    public void onNext(String s) {
         System.out.println(s); //打印出"Hello, world!"
    }  
  
    @Override  
    public void onCompleted() { }  
  
    @Override  
    public void onError(Throwable e) { }  
};  

不僅基本使用方式一樣,實質上,在 RxJavasubscribe 過程中,Observer 也總是會先被轉換成一個 Subscriber 再使用。所以如果你只想使用基本功能,選擇 ObserverSubscriber完全一樣的。它們的區別對于使用者來說主要有兩點:

  1. onStart(): 這是 Subscriber 增加的方法。它會在 subscribe 剛開始,而事件還未發送之前被調用,可以用于做一些準備工作,例如數據的清零或重置。這是一個可選方法,默認情況下它的實現為空。需要注意的是,如果對準備工作的線程有要求(例如彈出一個顯示進度的對話框,這必須在主線程執行),onStart() 就不適用了,因為它總是在 subscribe 所發生的線程被調用,而不能指定線程。要在指定的線程來做準備工作,可以使用 doOnSubscribe() 方法,具體可以在后面的文中看到。

  2. unsubscribe(): 這是 Subscriber 所實現的另一個接口 Subscription 的方法,用于取消訂閱。在這個方法被調用后,Subscriber 將不再接收事件。一般在這個方法調用前,可以使用 isUnsubscribed() 先判斷一下狀態。 unsubscribe() 這個方法很重要,因為在 subscribe() 之后, Observable 會持有 Subscriber 的引用,這個引用如果不能及時被釋放,將有內存泄露的風險。所以最好保持一個原則:要在不再使用的時候盡快在合適的地方(例如 onPause() onStop() 等方法中)調用 unsubscribe() 來解除引用關系,以避免內存泄露的發生。


Observable與Subscriber的關聯

這里subscriber僅僅就是打印observable發出的字符串。通過subscribe函數就可以將我們定義的myObservable對象和mySubscriber對象關聯起來,這樣就完成了subscriberobservable的訂閱。

myObservable.subscribe(myObserver);
// 或者:
myObservable.subscribe(mySubscriber);

一旦mySubscriber訂閱了myObservablemyObservable就是調用mySubscriber對象的onNextonComplete方法,mySubscriber 就會打印出Hello World!

訂閱(Subscriptions)

當調用Observable.subscribe(),會返回一個Subscription對象。這個對象代表了被觀察者和訂閱者之間的聯系。

Subscription subscription = Observable.just("Hello, World!")
    .subscribe(s -> System.out.println(s));

你可以在后面使用這個Subscription對象來操作被觀察者和訂閱者之間的聯系.

subscription.unsubscribe();//接觸訂閱關系
System.out.println("Unsubscribed=" + subscription.isUnsubscribed());
// Outputs "Unsubscribed=true"

RxJava的另外一個好處就是它處理unsubscribing的時候,會停止整個調用鏈。如果你使用了一串很復雜的操作符,調用unsubscribe將會在他當前執行的地方終止。不需要做任何額外的工作!


簡化代碼(Observable與Subscriber)

簡化Observable

是不是覺得僅僅為了打印一個hello world要寫這么多代碼太啰嗦?我這里主要是為了展示RxJava背后的原理而采用了這種比較啰嗦的寫法,RxJava其實提供了很多便捷的函數來幫助我們減少代碼。

首先來看看如何簡化Observable對象的創建過程。RxJava內置了很多簡化創建Observable對象的函數,比如Observable.just就是用來創建只發出一個事件就結束的Observable對象,上面創建Observable對象的代碼可以簡化為一行:

Observable<String> myObservable = Observable.just("Hello, world!"); //發送"Hello, world!"

其他方法:

1.使用just( ),將為你創建一個Observable并自動為你調用onNext( )發射數據:

justObservable = Observable.just("just1","just2");//依次發送"just1"和"just2"

2.使用from( ),遍歷集合,發送每個item:

List<String> list = new ArrayList<>();
list.add("from1");
list.add("from2");
list.add("from3");
fromObservable = Observable.from(list);  //遍歷list 每次發送一個
/** 注意,just()方法也可以傳list,但是發送的是整個list對象,而from()發送的是list的一個item** /

3.使用defer( ),有觀察者訂閱時才創建Observable,并且為每個觀察者創建一個新的Observable:

deferObservable = Observable.defer(new Func0<Observable<String>>() {
  @Override
  //注意此處的call方法沒有Subscriber參數
  public Observable<String> call() {
      return Observable.just("deferObservable");
  }});

4.使用interval( ),創建一個按固定時間間隔發射整數序列的Observable,可用作定時器:

intervalObservable = Observable.interval(1, TimeUnit.SECONDS);//每隔一秒發送一次

5.使用range( ),創建一個發射特定整數序列的Observable,第一個參數為起始值,第二個為發送的個數,如果為0則不發送,負數則拋異常:

rangeObservable = Observable.range(10, 5);//將發送整數10,11,12,13,14

6.使用timer( ),創建一個Observable,它在一個給定的延遲后發射一個特殊的值,等同于Android中Handler的postDelay( )方法:

timeObservable = Observable.timer(3, TimeUnit.SECONDS);  //3秒后發射一個值

7.使用repeat( ),創建一個重復發射特定數據的Observable:

repeatObservable = Observable.just("repeatObservable").repeat(3);//重復發射3次

簡化Subscriber

接下來看看如何簡化Subscriber,上面的例子中,我們其實并不關心OnComplete和OnError,我們只需要在onNext的時候做一些處理,這時候就可以使用Action1類。

Action1<String> onNextAction = new Action1<String>() {  
    @Override  
    public void call(String s) {  
        System.out.println(s);  
    }  
};  

subscribe方法有一個重載版本,接受三個Action1類型的參數,分別對應OnNext,OnComplete, OnError函數:

myObservable.subscribe(onNextAction, onErrorAction, onCompleteAction);  

這里我們并不關心onError和onComplete,所以只需要第一個參數就可以

myObservable.subscribe(onNextAction);  
// Outputs "Hello, world!"  

上面的代碼最終可以寫成這樣:

    Observable.just("Hello, world!")  
        .subscribe(new Action1<String>() {  
            @Override  
            public void call(String s) {  
                  System.out.println(s);  
            }  
        }); 

使用java8的lambda可以使代碼更簡潔:

不熟悉Lambda的可以看我之前寫的:Java8之Lambda表達式(Android用法)

    Observable.just("Hello, world!")  
        .subscribe(s -> System.out.println(s));  

簡單解釋一下這段代碼中出現的 Action1Action0Action0RxJava 的一個接口,它只有一個方法 call(),這個方法是無參無返回值的;由于 onCompleted() 方法也是無參無返回值的,因此 Action0 可以被當成一個包裝對象,將 onCompleted() 的內容打包起來將自己作為一個參數傳入 subscribe() 以實現不完整定義的回調。這樣其實也可以看做將onCompleted() 方法作為參數傳進了 subscribe(),相當于其他某些語言中的『閉包』。 Action1 也是一個接口,它同樣只有一個方法 call(T param),這個方法也無返回值,但有一個參數;與 Action0 同理,由于 onNext(T obj)onError(Throwable error)也是單參數無返回值的,因此 Action1 可以將 onNext(obj) 和 onError(error) 打包起來傳入 subscribe() 以實現不完整定義的回調。事實上,雖然 Action0 和 Action1 在 API 中使用最廣泛,但 RxJava 是提供了多個 ActionX 形式的接口 (例如 Action2, Action3) 的,它們可以被用以包裝不同的無返回值的方法。

注:正如前面所提到的,ObserverSubscriber 具有相同的角色,而且 Observersubscribe() 過程中最終會被轉換成 Subscriber 對象,因此,從這里開始,后面的描述我將用 Subscriber 來代替 Observer ,這樣更加嚴謹。

操作符(Operators)

操作符就是為了解決對Observable對象的 變換(關鍵詞) 的問題,操作符用于在Observable和最終的Subscriber之間修改Observable發出的事件。RxJava提供了很多很有用的操作符。
比如map操作符,就是用來把把一個事件轉換為另一個事件的。

map()操作符:

Observable.just("images/logo.png") // 輸入類型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 參數類型 String
            return getBitmapFromPath(filePath); // 返回類型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 參數類型 Bitmap
            showBitmap(bitmap);
        }
    });

使用lambda可以簡化為:

    Observable.just("images/logo.png") // 輸入類型 String
        .map(
                filePath -> getBitmapFromPath(filePath); // 返回類型 Bitmap
            )
        .subscribe(
                    bitmap -> showBitmap(bitmap);
                  );

可以看到,map() 方法將參數中的 String 對象轉換成一個 Bitmap 對象后返回,而在經過 map() 方法后,事件的參數類型也由 String 轉為了 Bitmap。這種直接變換對象并返回的,是最常見的也最容易理解的變換。不過 RxJava 的變換遠不止這樣,它不僅可以針對事件對象,還可以針對整個事件隊列,這使得 RxJava 變得非常靈活。

map()操作符進階:

    Observable.just("Hello, world!")  
        .map(s -> s.hashCode())  
        .map(i -> Integer.toString(i))  
        .subscribe(s -> System.out.println(s));  

是不是很酷?map()操作符就是用于變換Observable對象的,map操作符返回一個Observable對象,這樣就可以實現鏈式調用,在一個Observable對象上多次使用map操作符,最終將最簡潔的數據傳遞給Subscriber對象。

flatMap()操作符:

假設我有這樣一個方法:
這個方法根據輸入的字符串返回一個網站的url列表

Observable<List<String>> query(String text);   

Observable.flatMap()接收一個Observable的輸出作為輸入,同時輸出另外一個Observable。直接看代碼:

    query("Hello, world!")  
        .flatMap(new Func1<List<String>, Observable<String>>() {  
            @Override  
            public Observable<String> call(List<String> urls) {  
                return Observable.from(urls);  
            }  
        })  
        .subscribe(url -> System.out.println(url));  

這里我貼出了整個的函數代碼,以方便你了解發生了什么,使用lambda可以大大簡化代碼長度:

query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .subscribe(url -> System.out.println(url));  

flatMap()是不是看起來很奇怪?為什么它要返回另外一個Observable呢?理解flatMap的關鍵點在于,flatMap輸出的新的Observable正是我們在Subscriber想要接收的。現在Subscriber不再收到List<String>,而是收到一些列單個的字符串,就像Observable.from()的輸出一樣。

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

  1. 使用傳入的事件對象創建一個 Observable 對象;
  2. 并不發送這個 Observable, 而是將它激活,于是它開始發送事件;
  3. 每一個創建出來的 Observable 發送的事件,都被匯入同一個 Observable ,而這個 Observable 負責將這些事件統一交給 Subscriber 的回調方法。這三個步驟,把事件拆成了兩級,通過一組新創建的 Observable 將初始的對象『鋪平』之后通過統一路徑分發了下去。而這個『鋪平』就是 flatMap() 所謂的 flat

值得注意的是.from()是Observable創建時候用的,.flatMap()才是操作符;

其他操作符:

目前為止,我們已經接觸了兩個操作符,RxJava中還有更多的操作符,那么我們如何使用其他的操作符來改進我們的代碼呢?

更多RxJava的操作符請查看:RxJava操作符大全

getTitle()返回null如果url不存在。我們不想輸出"null",那么我們可以從返回的title列表中過濾掉null值!

query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .flatMap(url -> getTitle(url))  
    .filter(title -> title != null)  
    .subscribe(title -> System.out.println(title));  

filter()輸出和輸入相同的元素,并且會過濾掉那些不滿足檢查條件的。

如果我們只想要最多5個結果:

query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .flatMap(url -> getTitle(url))  
    .filter(title -> title != null)  
    .take(5)  
    .subscribe(title -> System.out.println(title));  

take()輸出最多指定數量的結果。

如果我們想在打印之前,把每個標題保存到磁盤:

query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .flatMap(url -> getTitle(url))  
    .filter(title -> title != null)  
    .take(5)  
    .doOnNext(title -> saveTitle(title))  
    .subscribe(title -> System.out.println(title));  

doOnNext()允許我們在每次輸出一個元素之前做一些額外的事情,比如這里的保存標題。

看到這里操作數據流是多么簡單了么。你可以添加任意多的操作,并且不會搞亂你的代碼。

RxJava包含了大量的操作符。操作符的數量是有點嚇人,但是很值得你去挨個看一下,這樣你可以知道有哪些操作符可以使用。弄懂這些操作符可能會花一些時間,但是一旦弄懂了,你就完全掌握了RxJava的威力。

感覺如何?

好吧,你是一個懷疑主義者,并且還很難被說服,那為什么你要關心這些操作符呢?

因為操作符可以讓你對數據流做任何操作。

將一系列的操作符鏈接起來就可以完成復雜的邏輯。代碼被分解成一系列可以組合的片段。這就是響應式函數編程的魅力。用的越多,就會越多的改變你的編程思維。

線程控制(Scheduler)

假設你編寫的Android app需要從網絡請求數據。網絡請求需要花費較長的時間,因此你打算在另外一個線程中加載數據。那么問題來了!

編寫多線程的Android應用程序是很難的,因為你必須確保代碼在正確的線程中運行,否則的話可能會導致app崩潰。最常見的就是在非主線程更新UI。

在不指定線程的情況下, RxJava 遵循的是線程不變的原則,即:在哪個線程調用 subscribe(),就在哪個線程生產事件;在哪個線程生產事件,就在哪個線程消費事件。如果需要切換線程,就需要用到 Scheduler (調度器)。

使用RxJava,你可以使用subscribeOn()指定觀察者代碼運行的線程,使用observerOn()指定訂閱者運行的線程

Scheduler 的 API

在RxJava 中,Scheduler ——調度器,相當于線程控制器,RxJava 通過它來指定每一段代碼應該運行在什么樣的線程。RxJava 已經內置了幾個 Scheduler ,它們已經適合大多數的使用場景:

  • Schedulers.immediate(): 直接在當前線程運行,相當于不指定線程。這是默認的 Scheduler。

  • 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。

  • 另外, Android 還有一個專用的 AndroidSchedulers.mainThread(),它指定的操作將在 Android 主線程運行。

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

  • subscribeOn(): 指定 subscribe() 所發生的線程,即 Observable.OnSubscribe 被激活時所處的線程。或者叫做事件產生的線程。

  • observeOn(): 指定 Subscriber 所運行在的線程。或者叫做事件消費的線程。

注意:observeOn() 指定的是 Subscriber 的線程,而這個 Subscriber 并不一定是 subscribe() 參數中的 Subscriber(這塊參考RxJava變換部分),而是 observeOn() 執行時的當前 Observable 所對應的 Subscriber ,即它的直接下級 Subscriber

換句話說,observeOn() 指定的是它之后的操作所在的線程。因此如果有多次切換線程的需求,只要在每個想要切換線程的位置調用一次 observeOn() 即可。

代碼示例:

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);
        }
    });

上面這段代碼中,由于 subscribeOn(Schedulers.io()) 的指定,被創建的事件的內容 1、2、3、4 將會在 IO 線程發出;
而由于 observeOn(AndroidScheculers.mainThread()) 的指定,因此 subscriber 數字的打印將發生在主線程
事實上,這種在 subscribe() 之前寫上兩句subscribeOn(Scheduler.io())observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常見,它適用于多數的 『后臺線程取數據,主線程顯示』的程序策略。

下面的實例,在Observable.OnSubscribecall()中模擬了長時間獲取數據過程,在SubscribernoNext()中顯示數據到UI。

    Observable.create(new Observable.OnSubscribe<String>() {
         @Override
         public void call(Subscriber<? super String> subscriber) {
             subscriber.onNext("info1");
      
             SystemClock.sleep(2000);
             subscriber.onNext("info2-sleep 2s");
      
             SystemClock.sleep(3000);
             subscriber.onNext("info2-sleep 3s");
      
             SystemClock.sleep(5000);
             subscriber.onCompleted();
         }
     })
    .subscribeOn(Schedulers.io()) //指定 subscribe() 發生在 IO 線程
    .observeOn(AndroidSchedulers.mainThread()) //指定 Subscriber 的回調發生在主線程
    .subscribe(new Subscriber<String>() {
        @Override
        public void onCompleted() {
            Log.v(TAG, "onCompleted()");
        }
      
        @Override
        public void onError(Throwable e) {
            Log.v(TAG, "onError() e=" + e);
        }
      
        @Override
        public void onNext(String s) {
            showInfo(s); //UI view顯示數據
        }
    });

至此,我們可以看到call()將會發生在 IO 線程,而showInfo(s)則被設定在了主線程。這就意味著,即使加載call()耗費了幾十甚至幾百毫秒的時間,也不會造成絲毫界面的卡頓。

值得注意:subscribeOn ()observeOn()都會返回了一個Observable,因此若不是采用上面這種直接流方式,而是分步調用方式,需要將新返回的Observable賦給原來的Observable,否則線程調度將不會起作用。

使用下面方式,最后發現“OnSubscribe”還是在默認線程中運行;原因是subscribeOn這類操作后,返回的是一個新的Observable。

observable.subscribeOn(Schedulers.io());
observable.observeOn(AndroidSchedulers.mainThread());
observable .subscribe(subscribe);

可以修改為下面兩種方式:

observable = observable.subscribeOn(Schedulers.io());
observable = observable.observeOn(AndroidSchedulers.mainThread());
observable .subscribe(subscribe);
//OR
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscribe);

前面講到了,可以利用 subscribeOn() 結合 observeOn() 來實現線程控制,讓事件的產生和消費發生在不同的線程。可是在了解了 map() flatMap() 等變換方法后,有些好事的(其實就是當初剛接觸 RxJava 時的我)就問了:能不能多切換幾次線程?

答案是:能。
因為 observeOn() 指定的是 Subscriber 的線程,而這個 Subscriber 并不是(嚴格說應該為『不一定是』,但這里不妨理解為『不是』)subscribe() 參數中的 Subscriber ,而是 observeOn() 執行時的當前 Observable 所對應的 Subscriber ,即它的直接下級 Subscriber 。換句話說,observeOn() 指定的是它之后的操作所在的線程。因此如果有多次切換線程的需求,只要在每個想要切換線程的位置調用一次 observeOn() 即可。上代碼:

    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() 呢?會有什么效果?

這個問題先放著,我們還是從 RxJava 線程控制的原理說起吧。

Scheduler 的原理

其實, subscribeOn()observeOn() 的內部實現,也是用的 lift()。具體看圖(不同顏色的箭頭表示不同的線程):

subscribeOn()原理圖:

observeOn() 原理圖:

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

最后,我用一張圖來解釋當多個 subscribeOn()observeOn() 混合使用時,線程調度是怎么發生的(由于圖中對象較多,相對于上面的圖對結構做了一些簡化調整):

圖中共有 5 處含有對事件的操作。由圖中可以看出,①和②兩處受第一個 subscribeOn() 影響,運行在紅色線程;③和④處受第一個 observeOn() 的影響,運行在綠色線程;⑤處受第二個 onserveOn() 影響,運行在紫色線程;而第二個 subscribeOn() ,由于在通知過程中線程就被第一個 subscribeOn() 截斷,因此對整個流程并沒有任何影響。這里也就回答了前面的問題:當使用了多個 subscribeOn() 的時候,只有第一個 subscribeOn() 起作用。

延伸:doOnSubscribe()

doOnSubscribe()一般用于執行一些初始化操作.

然而,雖然超過一個的 subscribeOn() 對事件處理的流程沒有影響,但在流程之前卻是可以利用的。

在前面講 Subscriber 的時候,提到過 SubscriberonStart() 可以用作流程開始前的初始化。然而 onStart() 由于在 subscribe() 發生時就被調用了,因此不能指定線程,而是只能執行在 subscribe() 被調用時的線程。這就導致如果 onStart() 中含有對線程有要求的代碼(例如在界面上顯示一個 ProgressBar,這必須在主線程執行),將會有線程非法的風險,因為有時你無法預測 subscribe() 將會在什么線程執行。

而與 Subscriber.onStart() 相對應的,有一個方法 Observable.doOnSubscribe() 。它和 Subscriber.onStart() 同樣是在 subscribe() 調用后而且在事件發送前執行,但區別在于它可以指定線程。默認情況下, doOnSubscribe() 執行在 subscribe() 發生的線程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的話,它將執行在離它最近的 subscribeOn() 所指定的線程。

示例:

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

如上,在 doOnSubscribe() 的后面跟一個 subscribeOn() ,就能指定準備工作的線程了。

RxJava 的適用場景和使用方式

RxJava + Retrofit

Retrofit 是 Square 的一個著名的網絡請求庫。對于Retrofit不了解的同學
可以參考我之前寫的文章:全新的網絡加載框架Retrofit2,上位的小三

Retrofit 除了提供了傳統的 Callback 形式的 API,還有 RxJava 版本的 Observable 形式 API。下面我用對比的方式來介紹 RetrofitRxJavaAPI 和傳統版本的區別。

以獲取一個 MovieEntity 對象的接口作為例子。使用Retrofit 的傳統 API,你可以用這樣的方式來定義請求:

    @GET("top250")
    Call<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);//正常返回Call對象

我們來寫getMovie方法的代碼:

    //進行網絡請求
    private void getMovie(){
        String baseUrl = "https://api.douban.com/v2/movie/";
    
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    
        MovieService movieService = retrofit.create(MovieService.class);
        Call<MovieEntity> call = movieService.getTopMovie(0, 10);
        call.enqueue(new Callback<MovieEntity>() {
            @Override
            public void onResponse(Call<MovieEntity> call, Response<MovieEntity> response) {
                resultTV.setText(response.body().toString());
            }
    
            @Override
            public void onFailure(Call<MovieEntity> call, Throwable t) {
                resultTV.setText(t.getMessage());
            }
        });
    }

以上為沒有經過封裝的、原生態的Retrofit寫網絡請求的代碼。

而使用 RxJava 形式的 API,定義同樣的請求是這樣的:

    @GET("top250")
    Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);//RxJava返回Observable對象

Retrofit本身對Rxjava提供了支持,getMovie方法改為:

    //進行網絡請求
    private void getMovie(){
        String baseUrl = "https://api.douban.com/v2/movie/";
    
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//提供RXjava支持
                .build();
    
        MovieService movieService = retrofit.create(MovieService.class);
    
        movieService.getTopMovie(0, 10)//返回Observable對象
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MovieEntity>() {
                    @Override
                    public void onCompleted() {
                        Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        resultTV.setText(e.getMessage());
                    }
    
                    @Override
                    public void onNext(MovieEntity movieEntity) {
                        resultTV.setText(movieEntity.toString());
                    }
                });
    }

這樣基本上就完成了RetrofitRxjava的結合,大家可以自己進行封裝;那么用上了RxJava,我們就可以用它強大的操作符來對數據進行處理和操作,各位看官可以具體去實現,我在這里不做多做贅述。

參考文章:RxJava 與 Retrofit 結合的最佳實踐

RxBinding

RxBindingJake Wharton 的一個開源庫,它提供了一套在 Android 平臺上的基于 RxJavaBinding API。所謂 Binding,就是類似設置 OnClickListener 、設置 TextWatcher 這樣的注冊綁定對象的 API

舉個設置點擊監聽的例子。使用 RxBinding ,可以把事件監聽用這樣的方法來設置:

Button button = ...;
RxView.clickEvents(button) // 以 Observable 形式來反饋點擊事件
    .subscribe(new Action1<ViewClickEvent>() {
        @Override
        public void call(ViewClickEvent event) {
            // Click handling
        }
    });

看起來除了形式變了沒什么區別,實質上也是這樣。甚至如果你看一下它的源碼,你會發現它連實現都沒什么驚喜:它的內部是直接用一個包裹著的 setOnClickListener() 來實現的。然而,僅僅這一個形式的改變,卻恰好就是 RxBinding 的目的:擴展性。通過 RxBinding 把點擊監聽轉換成 Observable 之后,就有了對它進行擴展的可能。擴展的方式有很多,根據需求而定。一個例子是前面提到過的 throttleFirst() 操作符,用于去抖動,也就是消除手抖導致的快速連環點擊:

RxView.clickEvents(button)
    .throttleFirst(500, TimeUnit.MILLISECONDS)
    .subscribe(clickAction);

如果想對 RxBinding 有更多了解,可以去它的 GitHub 項目 下面看看。

RxLifecyle

RxLifecycle 配合 Activity/Fragment 生命周期來管理訂閱的。 由于 RxJava Observable 訂閱后(調用 subscribe 函數),一般會在后臺線程執行一些操作(比如訪問網絡請求數據),當后臺操作返回后,調用 ObserveronNext 等函數,然后在 更新 UI 狀態。 但是后臺線程請求是需要時間的,如果用戶點擊刷新按鈕請求新的微博信息,在刷新還沒有完成的時候,用戶退出了當前界面返回前面的界面,這個時候刷新的 Observable 如果不取消訂閱,則會導致之前的 Activity 無法被 JVM 回收導致內存泄露。 這就是 Android 里面的生命周期管理需要注意的地方,RxLifecycle 就是用來干這事的。比如下面的示例:

myObservable
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
    .subscribe();

這樣Activitydestroy的時候就會自動取消這個observer

RxBus

RxBus并不是一個庫,而是一種模式。相信大多數開發者都使用過EventBus或者Otto,作為事件總線通信庫,如果你的項目已經加入RxJavaEventBus,不妨用RxBus代替EventBus,以減少庫的依賴。RxJava也可以輕松實現事件總線,因為它們都依據于觀察者模式。

拓展鏈接:
用RxJava實現事件總線(Event Bus)
[深入RxBus]:支持Sticky事件

RxPermission

RxPermission是基于RxJava開發的用于幫助在Android 6.0中處理運行時權限檢測的框架。在Android 6.0中,系統新增了部分權限的運行時動態獲取。而不再是在以前的版本中安裝的時候授予權限。

拓展鏈接:
使用RxPermission框架對android6.0權限進行檢測

總結

簡而言之Rxjava是一個很牛逼的庫,如果你的項目中還沒有使用RxJava的話,建議可以嘗試去集成使用;對大多數人而已RxJava是一個比較難上手的庫了,不亞于Dagger的上手難度;不過當你認識學習使用過了,你就會發現RxJava的魅力所在;如果看一遍沒有看懂的童鞋,建議多看幾次;動手寫寫代碼,我想信本文可以給到你們一些幫助;你們真正的體會到什么是 從入門到放棄再到不離不棄 ;這就是RxJava的魅力所在。

拓展閱讀:

我所理解的RxJava——上手其實很簡單

深入淺出RxJava - 大頭鬼

給 Android 開發者的 RxJava 詳解 - 拋物線

如果有疑問和見解,也歡迎大家在下面留言,我會一一回復大家

以上

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容