RxJava學習系列(一)--使用

注:本系列文章主要用于博主個人學習記錄,本文末尾附上了一些較好的文章提供學習。
轉載請附 原文鏈接
RxJava學習系列(一)--使用
RxJava學習系列(二)--原理

RxJava利用響應式編程思想,專注于異步任務的處理,通過操作符進行流式操作,可以極大的去除多層嵌套,達到邏輯的簡潔明了。

舉個栗子??


模擬用戶注冊請求

RxJava的觀察者模式與普通觀察者模式有一個區別是分為“冷”啟動和“熱”啟動,“熱”啟動即不管有沒有觀察者,觀察者會按照自己的邏輯發送數據,而“冷”啟動則是只有當觀察者開啟訂閱時才開始發送數據。

1.基本概念及用法

  • 三個重要的對象

    • Observable-數據發送者
    • Subscriber-訂閱者
    • OnSubscribe-事件

    一次事件訂閱的流程:Observable持有一個 OnSubscribe對象,事件在OnSubscribe對象中被執行,當有Subscriber訂閱了這個 Observable時,OnSubscribe中的事件開始執行,并由Observable發射數據給Subscriber

一次完整訂閱
  • Subscriber對象

    Subscriber是一個抽象類,需要實現三個方法

    • onCompleted(): 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的onNext() 發出時,需要觸發 onCompleted() 方法作為標志。
    • onError(): 事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。
    • 在一個正確運行的事件序列中, onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。

    引用自扔物線

    在代碼中調用了onComplete后再調用onNext,依然可以發送數據,onComplete會在發送完所有的數據后才被調用

    當onError被調用了,即使在出現錯誤之前調用onNext依然不會成功,只會觸發onError

    ?

    Subscriber<String> subscriber = new Subscriber<String>() {
                @Override
                public void onCompleted() {
                  //數據發送完畢
                }
    
                @Override
                public void onError(Throwable e) {
                  //數據發送出錯
                }
    
                @Override
                public void onNext(String data) {
                  //數據發送成功
                }
            };
    

    通過泛型指定Subscriber所能接收的數據,在onNext 中處理相應的邏輯,此處需要注意的是: onNext方法調用的次數取決于OnSubscribe中被調用的次數

    • Action1<T>

      在某些情況下我們不需要在每一個回調中都處理邏輯,可能只需要訂閱onNext,就可以實現Action1<T>

    new Action1<Object>() {
                @Override
                public void call(Object obj) {
                  //有參數的回調onError和onNext
                }
    };
    new Action0(){
          @override
          public void call(){
              //onComplete
      }
    };
    
  • Observable對象

    • Observable.create(OnSubscribe<T> onSubscribe);

    create方法傳入一個OnSubscribe對象,在call方法中發送數據,這是最基礎的創建方法

    Observable.create(new Observable.OnSubscribe<String>() {
                @Override
                public void call(Subscriber<? super String> subscriber) {
                    subscriber.onNext("create success");
                }
            });
    
    • Observable.just( T1 t1, T2 t2, T3 t3,…)

    just方法允許快速創建隊列,每一個參數會調用onNext方法傳遞一次(最多10個),且按順序發送,just在發送完數據后,會調用onComplete

    Observable.just("one","two","three");
    
    • from

    from可以將數組,Iterable,Future對象轉換為 Observable對象,發送數據

    String [] array = new String[]{"one-from","two-from","three-from"};
    Observable.from(array);
    

    創建一個Observable的方法有很多,不一一列舉


    創建Observable
  • 訂閱事件

    • Observable.subcribe()--return Subscription;

    通過subscribe來開啟訂閱,此時Observable開始發送數據,并且返回一個Subscription對象

    • Subscription

    Subscription是一個接口,有兩個方法unsubscribe()和isUnsubscribe(),在訂閱事件時返回這個對象,可以在需要的時候取消掉訂閱,在android開發中能簡單有效的避免內存溢出。

2.線程控制

上面所提到的訂閱會默認在當前線程中執行,然并卵,既然是專注于異步操作,就一定有線程控制的方法

  • Schedulers—線程調度


    Sckedulers的各種線程
  • subscribeOn—被觀察事件執行線程(事實上,在該方法調用之前,以及調用后,observeOn之前的代碼都會在subscribeOn所在的線程中執行)

  • observeOn— 觀察線程(可以多次轉換,observeOn指定在它之后的代碼線程)

  • 實踐

    該方法只應該被調用一次,如果調用多次,只有第一個會生效 !


    多次調用subscribeOn

如上圖,首先指定了subscribeOn的線程為io線程,然后又指定了計算線程,打印日志


logcat 1

通過日志打印可以發現,只有第一個subscribeOn生效,并且在observeOn之前的代碼也都在io線程中執行,而在observeOn之后的代碼,在每一次調用該方法后都改變了線程

有好事的同學說了,那如果我先調用observeOn再調用subscribeOn呢?雖然沒有人這么做,但嚴謹的我還是要試試


observeOn

先調用observeOn指定為主線程,然后subscribeOn指定為ui線程


E1EB67D4-11D4-42D2-A26F-5AB4282263A1.jpg

可以看到第一條日志在io線程中執行,而第二條日志在主線程中,似乎可以得到一個結論

observeOn指定在它之后的代碼的執行線程,而其余代碼均在第一個subscribeOn指定的線程中執行

3.操作符

A.轉換操作

  • map()— 將發射的數據轉化為subscriber所需要的數據

    Observable.create(new Observable.OnSubscribe<Integer>() {
                @Override
                public void call(Subscriber<? super Integer> subscriber) {
                    subscriber.onNext(random());
                    subscriber.onCompleted();
                }
            }).map(new Func1<Integer, String>() {
                @Override
                public String call(Integer integer){
                    if(integer == 0){
                        return "the number is 1";
                    }else{
                        return "the number is not 1";
                    }
                }
            });
    

    栗子中的代碼是在原始的Observable中發射類型為Integer的數據,通過map操作后,subscriber所接收到的數據為 String 類型,map中需要傳入Fun1<T,E>,參數1表示上一個操作符操作后所發送的數據,一個Observable可以進行多次轉化操作,subscriber接收到的數據為最后一次轉化發射的數據。

    注意:map操作轉換的是發射的數據,Observable本身并不會被轉換

  • flatmap()— 將一個Observable轉換為一個新的Observable,并且由這個新的Observable發射數據

    Observable.create(new Observable.OnSubscribe<Integer>() {
                @Override
                public void call(Subscriber<? super Integer> subscriber) {
                    subscriber.onNext(random());
                    subscriber.onCompleted();
                }
            }).flatMap(new Func1<Integer, Observable<String>>() {
                @Override
                public Observable<String> call(Integer integer) {
                    return Observable.just("new Observable"+integer);
                }
            });
    

    flatMap 同樣需要傳入Func1<T,E> 與map不同的是,返回的是一個Observable對象,而subscriber所訂閱的應該是這個新的Observable,flatmap也可以多次調用多次轉換,問題來了~subscriber只關心接收到的數據,并不關心訂閱的具體是哪一個Observable,那flatMap和map的應用場景是什么呢?

  • 應用場景

    • map比較簡單,一個Observable可能被多個subscriber訂閱,而不同的訂閱所需要的最終數據不同,但事件的操作邏輯是相同的,就可以利用map來滿足不同的數據需求
    • flatmap的用處就比較多了,文章最開頭舉的栗子,一次復雜的注冊邏輯,首先要請求服務器獲取token,獲取token后注冊請求,注冊請求完成后,登錄請求,每一次請求利用Retrofit封裝返回一個 Observable對象.我們只關心最后登錄成功后告知用戶,并刷新UI。這樣原本用回調至少嵌套兩次的邏輯,變得清晰明了(這樣的注冊邏輯本身是有問題的~)


      模擬一次注冊請求

      ![Uploading 48862518-1B43-457E-B734-36AE6669893C_441752.jpg . . .]

注意:只有每一個Observable都成功發射數據后,才會調用onNext方法,如果出現異常會直接調用onError

這樣看來,好像很雞肋,后面會講到錯誤操作,你會發現RxJava確實是很牛逼的啊~

  • 其它轉換操作


    轉換操作符

B.合并操作

  • merge— 將多個Observable合并為一個Observable發射數據

    Observable.merge(observable1,observable2,observable3,observable4);
    

    官方文檔說明:merge可能會導致交錯發射數據,即不是按照合并順序來發射數據

    同樣,一旦有一個Observable發射異常,會立即觸發onError,RxJava的實現中有一個mergeDelayError— 只有當所有的數據都發射完畢后才會調用onError

  • concat— 將多個Observable合并為一個Observable并且按順序發射數據

    Observable.concat(observable1,observable2,observable3,observable4)
    
  • zip— 將多個Observables的發射物結合到一起,基于這個函數的結果為每個結合體發射單個數據項。

    Observable.zip(
                    Observable.just("1"), 
                    Observable.just("2"), 
                    Observable.just("3"), 
                    Observable.just("4"), 
                    new Func4<String, String, String, String, String>() {
                        @Override
                        public String call(String s, String s2, String s3, String s4) {
                            return s+s2+s3+s4;
                        }
                    });
    

    zip傳入需要合并的Observable對象,以及 Func4<T,...,Object>,與merge不同的是,zip是將所有發射的數據拿到后,進行整合,最后發射這個整合后的數據。call中的參數是嚴格按照合并順序所發射的數據,return的即為最終發射的數據

    zip的不僅可以合并發射源,并且可以根據需要轉換最終發射的數據類型

C.過濾操作

假設這樣一種場景,加載數據的時候先向服務器請求,如果成功就顯示,如果失敗就查找緩存數據。很容易想到可以利用合并操作符來處理,但是合并操作會依次發射數據,這不是我們所希望的。這里就需要用的過濾操作了

  • filter — 只發射通過了謂詞測試的數據項

    filter根據規則去檢驗數據,只要通過了檢驗的數據都會被發射,直到檢驗完最后一個 Observable

    .filter(new Func1<Integer, Boolean>() {
                        @Override
                        public Boolean call(Integer i) {
                          //只發射小于等于5的數據
                          return i<=5;
                        }
                    })
    
  • first — 只發射第一項(或者滿足某個條件的第一項)數據

    first類似于filter,不同的是,只發射第一個通過檢驗的數據

    first()//只發射第一個數據
    first(Func1)//滿足某個條件的第一個發射成功的數據
    .first(new Func1<String, Boolean>() {
                        @Override
                        public Boolean call(String s) {
                            return false;
                        }
                    })
    

    所有發射的數據,會在call中按照規則進行檢驗,比如當第一個傳過來的字符串不為空時,就認為發射數據成功,那么應該return true,當return為true的時候會調用onNext方法,而還沒有發射數據的Observable將不再發射數據,如果return為false,那么會依次檢驗后面的Observable是否發射數據成功,直到return true或者全部不符合調onError

  • last — 只發射最后一條(或者滿足某個條件的最后一項)數據

    last的用法跟frist一毛一樣。

  • 其它過濾操作


    過濾操作符

D.異常處理

異常處理

onErrorReturn可以在異常發生時發射一個默認的數據,結合過濾操作,可以發射一個不符合規則的數據,避免中斷數據發射

E.doOn

有一種場景,比如說請求到數據后寫入緩存,但是不希望訂閱者去處理,因為如果多處訂閱必然會產生重復代碼并且可能阻塞主線程,doOn的系列操作就派上了用場

  • doOnNext — 當數據發射成功時調用

    Observable.just("data to shoot")
                    .doOnNext(new Action1<String>() {
                        @Override
                        public void call(String s) {
                            //發射成功后需要的操作
                              writeToCache(s);
                        }
                    })
    

    在使用的時候注意判斷doOnNext當前在哪個線程執行

  • doOnError — 當發生異常時調用
  • doOnSubscribe — 當被訂閱時調用
  • doOnTerminate — 發射數據完畢后調用

F.封裝-compose

當我們真正開始使用RxJava來替換之前的邏輯代碼時,我們發現僅用現有的操作符無法做到完全的簡潔,依然會出現一些不必要的重復代碼和邏輯。適度的封裝也是必要的,RxJava早就想到了這點,提供了一個操作符來封裝一些通用代碼

  • compose(@NotNull Transformer<T,R>)

    compose方法需要傳入一個變形金剛對象,其中泛型T為原始的Observable所發射的數據類型,R為變形后所發射的數據類型,舉個栗子,比如封裝一個方法在io線程中發射數據,在ui線程中觀察接收數據。

    public <T> Observable.Transformer<T,T> io_main(){
            return new Observable.Transformer<T, T>() {
                @Override
                public Observable<T> call(Observable<T> tObservable) {
                    return tObservable.subscribeOn(Schedulers.io())
                            .observeOn(AndroidSchedulers.mainThread());
                }
            };
        }
    

    創建一個Transformer對象,需要實現call方法,return一個新的Observable,然后傳入到compose()中:

    Observable.just("123")
                    .compose(this.<String>io_main())
                    .subscribe();
    

    查看一下compose的源碼發現,它其實就做了一件事情,調用 Transformer的call方法,并把當前的Observable對象作為參數傳進去,返回call方法得到的新的Observable

     public <R> Observable<R> compose(Transformer<? super T, ? extends R> transformer) {
            return ((Transformer<T, R>) transformer).call(this);
        }
    

    ?

4.RxBus

EventBus讓模塊間的耦合更低,利用 RxJava實現EventBus簡直是容易,且便于管理

在實現RxBus之前介紹兩個很重要的類

  • CompositeSubscription— Subscription that represents a group of Subscriptions that are unsubscribed together.

    是Subscription的一個實習類,用于管理一組訂閱,當取消訂閱時,會將這一組訂閱全部取消,在Android中可以利用該類管理一個Activity中所有的異步任務,當Activity被銷毀時,取消訂閱,避免內存泄漏

    • add — 將一個訂閱加入到一個訂閱組中
    • remove — 將一個訂閱從該組中移除
    • clear — 清空訂閱組
    • unsubscribe — 取消改組中正在進行的所有訂閱
  • Subject — 一個神奇的類,在 RxJava中它同時充當了Observer和Observable的角色。

    文章一開頭提到了“冷”啟動和“熱”啟動,而一個Subject可以將一個“冷”Observer變成一個“熱”的,因為它是一個Observer,它可以訂閱一個或多個Observable;又因為它是一個Observable,它可以轉發它收到(Observe)的數據,也可以發射新的數據。

    subject.subscribe(subscriber) — 訂閱事件

    subject.onNext(obj) — 發射數據

    Subject 在RxJava中總共有7個子類,這里不一一介紹(因為我也沒用過…)

    • PublishSubject — 一個“熱”的Observable,這個對象會在onNext被調用的時候就開始發射數據,無論有沒有訂閱者,當一個Observer訂閱了這個對象時,只會收到訂閱時間點之后所發射的數據。官方給出的栗子:

      兩個observer分別訂閱了同一個subject,observer1會收到所有的數據,而observer2只能收到最后一條數據

    PublishSubject<Object> subject = PublishSubject.create();
      // observer1 will receive all onNext and onCompleted events
      subject.subscribe(observer1);
      subject.onNext("one");
      subject.onNext("two");
      // observer2 will only receive "three" and onCompleted
      subject.subscribe(observer2);
      subject.onNext("three");
      subject.onCompleted();
    

    再想想EventBus的原理,我們所需要的正是這樣一個“熱”Observable。這里是較為復雜的一種實現,先上原理圖


    RxBus原理

再貼代碼

private ConcurrentHashMap<Object, List<Subject>> subjectMapper 
  = new ConcurrentHashMap<Object, List<Subject>>();
//訂閱事件
public <T> Observable<T> subscribe(@NonNull Object tag) {
        List<Subject> subjectList = subjectMapper.get(tag);
        if (null == subjectList) {
            subjectList = new ArrayList<Subject>();
            subjectMapper.put(tag, subjectList);
        }
        Subject<T, T> subject;
        subjectList.add(subject = PublishSubject.create());
        return subject;
    }
//發布事件--發射數據
public void post(@NonNull Object tag, @NonNull Object content) {
        List<Subject> subjectList = subjectMapper.get(tag);
        if (!isEmpty(subjectList)) {
            for (Subject subject : subjectList) {
                subject.onNext(content);
            }
        }
    }

RxBus的核心邏輯就完成了,當然還需要加上取消訂閱,清空事件等代碼,比較簡單不再贅述,在我實際的項目開發中,我將 RxBus交由 RxJavaManager進行管理,所有的訂閱事件全部經過 RxJavaManager來操作,在需要取消訂閱的地方統一unsubscribe

5.資料

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

推薦閱讀更多精彩內容