Rx系列之Rxjava操作符進階-使用場景

通過上一篇《Rx系列之RxJava操作符》,相信已經能夠熟練的使用一些基本的操作符了。但是對于我們大家而言,其實最傳統的命令式編程已經是我們順手就可以拈來的,但是,現在用響應式編程,突然發現:臥槽,這個地方用響應式怎么寫,這樣寫對么?估計很多人才開始接觸RxJava的時候應該都有這樣的疑慮。不用擔心,這一篇就給大家講講RxJava到底該怎么用,在什么情況下用!

RxJava的使用場景

眼尖的小伙伴,可能已經發現,在上一篇中,很多那么重要的操作符怎么都沒講!哈哈哈,答案在這里。好廢話不多說,來看看Rxjava到底在哪些情況下可以使用。

動態搜索的場景

我們先來看一個動態搜索的場景:

搜索

假設,我要在這進行網絡搜索,那么,我就要在這里面進行網絡訪問,如果是輸入完成之后點擊確定進行搜索還好,但是如果是動態收索呢?只要搜索框中的搜索內容一改變,那么是不是就要進行網絡請求呢?那這樣就不是那么友好了。為了解決這樣的問題,rxjava為我們提供了一個很好的解決方案:

  • 使用debounce作為textSearch
    debounce()函數過濾掉由Observable發射的速率過快的數據;如果在一個指定的時間間隔過去了仍舊沒有發射一個,那么它將發射最后的那個。
    debounce()使用TimeUnit對象指定時間間隔。
    是不是感覺棒棒噠,昂,不管你喜不喜歡,反正我是愛死它了。

來看一下示意圖


debounce示意圖
debounce示意圖

由上圖我們可以看出,在比較密集的數據(2,3,4,5)發射之后,其實最終只是發射5。

附上代碼:

        RxTextView.textChanges(editText)
                .debounce(5000,TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<CharSequence>() {
                    @Override
                    public void onCompleted() {
                        Log.d(TAG, "onCompleted: onCompleted");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "onError: onError");
                    }

                    @Override
                    public void onNext(CharSequence charSequence) {
                        Log.d(TAG, "onNext: "+charSequence.toString());
                    }
                });

在這5s內,我輸入了2,3,4,5(出最后一個5,其他輸入之后就刪除哈),但是最后得到的結果卻是:

onNext: 5

注意:這個操作符會會接著最后一項數據發射原始Observable的onCompleted通知,即使這個通知發生在你指定的時間窗口內(從最后一項數據的發射算起)。也就是說,onCompleted通知不會觸發限流。

在上面可能會存在一個疑問,那就是
RxTextView.textChanges(editText)
這是個什么東西?你丫怎么沒講,哈哈,這個呀,要在后面的Rx系列中單獨來講,所以不要著急,暫時說一下這個的功能,這個RxTextView.textChanges(editText)其實是RxBinding里面的一個對控件的操作,其功能就跟TextWatcher一樣,就是對數據的變更進行監聽,所以上面的數據變化之后5s后將數據發射出去。嗯嗯,到這里就把動態搜索場景講解了。

緩存檢測場景

在請求取數據的處理過程中,我們的操作一般是這樣一個原理:

  • ** 首先檢查內存是否有緩存**
  • 然后檢查文件緩存中是否有
  • 最后才從網絡中取
    任何一步一旦發現數據后面的操作都不執行
    在rxjava中為我們提供了兩個解決這個問題的操作符,分別是: concatfirst

concat
不交錯的發射兩個或多個Observable
concat操作符連接多個Observable的輸出,就好像它們是一個Observable,第一個Observable發射的所有數據在第二個Observable發射的任何數據前面,以此類推。直到前面一個Observable終止,Concat
才會訂閱額外的一個Observable

請注意上面所說的“就好像它們是一個Observable”,其實并不是一個Observable,是前面一個停止之后才會訂閱下一個,所以說他們并不是一個,請君注意咯。

concat示意圖
concat示意圖

如上所示,就是將兩個Observable連接起來了。
還有一個實例方法concatWith,它是和concat等價的:Observable.concat(a,b)==a.concatWith(b)

來看一下是不是這個樣子的:

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: " + integer);
            }
        };

        Observable a = Observable.just(1, 2, 3, 4, 5);
        Observable b = Observable.just(6, 7, 8, 9, 10);

        Observable.concat(a, b)
                .subscribe(subscriber);

然后我們得到:

onNext: 1
 ...
onNext: 10
onCompleted: onCompleted

這時估計就會有人說了:你不是說這個操作符其實是將兩個訂閱連接起來了嘛!那么,為什么只是在最后打印了onCompleted,在onNext: 5后面不是也應該打印一個嗎?
我們都知道觀察者和被觀察者之間,是由訂閱建立關系的,那么對于被觀察者來說,確實我發射了兩個數據源,但是對于觀察者來說,我不知道你有幾個數據源,我的職責就只是,數據發射過來后,我打印而已。所以,只有當onNext沒有接收到數據時,才會調用onCompleted

最后對這個操作符,再補充一點:如果當第一個Observable a拋異常,那么將不會繼續執行后面的Observable b了。
如果想測試請將上面的

Observable a = Observable.just(1, 2, 3, 4, 5);

變成

Observable a = Observable.just(1, 2, 3, 4, new RuntimeException());

進行測試。

first
只發射第一項(或者滿足某個條件的第一項)數據

first示意圖
first示意圖

由上圖我們可以看出,這個只要第一項滿足條件,后面的將不會再進行發射,所以只是得到了1這個數字。

        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: " + integer);
            }
        };

        Observable a = Observable.just(1, 2, 3, 4, 5);
        a.first().subscribe(subscriber);

得到結果:

onNext: 1
onCompleted: onCompleted

這個應該很容易就看出來了。就是只是打印了第一個數據!
在這兒必須為大家區別一個操作符:single(),這個操作符也是只打印一個數據的,但是single()和first()最大的區別在于:前者只會發射一個數據,不能發射多個,否則會報錯;而first確實滿足條件的那一個。
如下:

    Observable a = Observable.just(1);
    a.single().subscribe(subscriber);

估計到這兒應該已經有人知道了上面的3個步驟改真沒寫了,來我們來看看代碼:

 final Observable<String> memory = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                if (memoryCache != null) {
                    subscriber.onNext(memoryCache);
                } else {
                    subscriber.onCompleted();
                }
            }
        });
        Observable<String> disk = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                String cachePref = rxPreferences.getString("cache").get();
                if (!TextUtils.isEmpty(cachePref)) {
                    subscriber.onNext(cachePref);
                } else {
                    subscriber.onCompleted();
                }
            }
        });

        Observable<String> network = Observable.just("network");

        //依次檢查memory、disk、network  
        Observable
                .concat(memory, disk, network)
                .first()
                .subscribeOn(Schedulers.newThread())
                .subscribe(s -> {
                    memoryCache = "memory";
                    System.out.println("--------------subscribe: " + s);
                });

現在看上面的代碼是不是就知道它在干什么了,是不是很簡單!這個緩存檢測場景就講到這里。

輸入合法場景

在某些時候,我們需要所以的輸入都合法后,我們的某些按鈕才亮起來,或者才能點擊,如下圖:


輸入合法示意圖

在這個場景中,我們得掌握兩個操作符:skipcombineLatest

skip
抑制Observable發射的前N項數據

skip示意圖
skip示意圖

從上圖可以看到,總共發射了4個數據,只有最后兩個發射出去了,這就是skip(2)的作用。

        Observable.just(1,2,3,4).skip(2).subscribe(new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted: onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: onError");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "onNext: "+integer);
            }
        });

得到如下結果:

onNext: 3
onNext: 4
onCompleted: onCompleted

combineLatest
當多個Observables中的任何一個發射了數據時,使用一個函數結合每個Observable發射的最近數據項,并且基于這個函數的結果發射數據。
CombineLatest在原始的Observable中任意一個發射了數據時發射一條數據。當原始Observables的任何一個發射了一條數據時,CombineLatest
使用一個函數結合它們最近發射的數據,然后發射這個函數的返回值。
一開始看到這句話,我又懵b了,這tm幾個意思?我們先來看看這個場景的實現代碼,然后再解釋:

        private void combineLatestEvent() {

        Observable<CharSequence> usernameObservable = RxTextView.textChanges(mUsername).skip(1);
        Observable<CharSequence> emailObservable = RxTextView.textChanges(mEmail).skip(1);
        Observable<CharSequence> passwordObservable = RxTextView.textChanges(mPassword).skip(1);

       Subscription subscription = Observable.combineLatest(usernameObservable, emailObservable,
                passwordObservable,
                new Func3<CharSequence, CharSequence, CharSequence, Boolean>() {
                    @Override
                    public Boolean call(CharSequence userName, CharSequence email, CharSequence
                            password) {

                        boolean isUserNameValid = !TextUtils.isEmpty(userName) && (userName
                                .toString().length() > 2 && userName.toString().length() < 9);

                        if (!isUserNameValid) {
                            mUsername.setError("用戶名無效");
                        }


                        boolean isEmailValid = !TextUtils.isEmpty(email) && Patterns
                                .EMAIL_ADDRESS.matcher(email).matches();

                        if (!isEmailValid) {
                            mEmail.setError("郵箱無效");
                        }

                        boolean isPasswordValid = !TextUtils.isEmpty(password) && (password
                                .toString().length() >5 && password.toString().length() < 11);

                        if (!isPasswordValid) {
                            mPassword.setError("密碼無效");
                        }


                        return isUserNameValid && isEmailValid && isPasswordValid;
                    }
                })
                .subscribe(getObserver());
    }

  
    private Observer<Boolean> getObserver() {
        return new Observer<Boolean>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Boolean aBoolean) {
                //更改注冊按鈕是否可用的狀態
                register.setEnabled(aBoolean);
            }
        };
    }

這個場景,有3個edittext,分別是mUsername,mEmail,mPassword,通過輸入合法的內容進行判定注冊按鈕是否亮起來。
當我點擊其中的任何一個進行編寫的時候,就會發射數據,發射的是什么?是我們編輯的內容嗎?其實不是的,發射的是結合Func3這個方法的返回值,在這里這個返回值是Boolean型的。返回了boolean型之后,就可以在觀察者里面設置注冊按鈕是否亮起來。現在再看上面那句高深莫測的話,是不是簡單多了!

這兒可能有人有疑問了:這3個edittext為什么要使用skip(1)呢?
答案其實很簡答啊,那就是當我們不寫skip(1)的時候,edittext中沒有輸入任何值的時候,會把它當作第一個數據進行發射,雖然發射的是個空數據,但是還是會發射啊!
奧偶,這個場景解釋完了!

數據過期場景

其實這個場景可以和上面的數據緩存檢測場景進行合并:在緩存檢測場景中,我們知道,如果memory中沒有數據,就從disk上面尋找,然后再是網絡請求,那么,問題來了,如果我們的memory中一直有數據,但是網絡數據已經變更了,又由于緩存檢測原則的只要有一個有數據就不會進行網絡請求了,這就會造成我們顯示的數據一直是一個舊數據。

哦豁

那這個該怎么辦呢?
解決方法有如下兩個:

  • 采用定時進行清除本地緩存數據
  • 采用過濾操作符

我們先來看看第一種,如果是進行定時做本地數據清空的話,那么就會用到,我們一個輪詢的操作符Interval
創建一個按固定時間間隔發射整數序列的Observable
Interval通俗的講,就是每隔一段時間過后做什么事情!上一篇已經講過了,所以這里就不詳細講解了,直接上代碼:

        Observable.interval(3, TimeUnit.SECONDS).subscribe(new Observer<Long>() {
           ...
           @Override
           public void onNext(Long aLong) {
                //清除緩存操作
           }
       });

很多人可能會想到,那既然,我能夠用清除本地緩存的方法,那么能不能用,每隔一段時間進行請求,讓請求的結果與本地緩存進行合并呢?
答案是肯定的,來看如下代碼:

    Observable.create(new Observable.OnSubscribe<String>() {  
            @Override  
           public void call(final Subscriber<? super String> observer) {  
 
              Schedulers.newThread().createWorker()  
                    .schedulePeriodically(new Action0() {  
                          @Override  
                          public void call() {  
                              observer.onNext(doNetworkCallAndGetStringResult());  
                         }  
                      }, INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS);  
            }  
        }).subscribe(new Action1<String>() {  
           @Override  
           public void call(String s) {  
               
           }  
      })  

這個就是使用schedulePeriodically做輪詢請求

這樣造成每過一定時間我們就會,清除緩存或者網絡請求。讀到這兒,是不是感覺這個方法真爛,哈哈哈,不著急,我們不是還有第二種方法嘛!來接著看第二種方法

  • 采用過濾操作符
    其實這個操作符我們已經講過了,那就是操作符first,回顧一下上面的代碼,就是我們的first就是保證,眾多的數據,有一個符合條件就發射數據,后面的都將不執行。我們的是否需要更新的條件不加在這里,就沒天理咯!
Observable source = Observable
    .concat(memory, disk, network)
    .first(new Func1() {
      @Override public Boolean call(Data data) {
        return data.isUpToDate();
      }
    });

哇偶,這個操作符完美的解決了如上的問題!那你丫的還將那么多,呵呵,我只是給大家講解操作符的使用場景而已,那個適合哪個場景,取決你們自己咯!

這一篇主要講解的內容的就到這兒了,下面還有一些其他的場景,就簡單的介紹一下。

其他的場景

合并兩個數據源場景

使用merge合并兩個數據源,代碼如下:

    Observable.merge(getInfoFromFile(), getInfoFromNet())  
           .observeOn(AndroidSchedulers.mainThread())  
              .subscribe(new Subscriber<String>() {  
                  @Override  
                  public void onCompleted() {  
                     Log.d(TAG, "onCompleted: onCompleted");
                 }  
 
                 @Override  
                  public void onError(Throwable e) {  
                    Log.d(TAG, "onError: onError");
                  }  
  
                  @Override  
                  public void onNext(String data) {  
                       Log.d(TAG, "onNext: only one ! ");
             });  

Retrofit結合RxJava場景

這個場景的話,大家可以查看扔物線大神寫的給 Android 開發者的 RxJava 詳解,其中講解到了這個場景的結合!

就操作符使用場景這一塊而言,大概就講解這么多,如果大家有其他的使用場景,我們可以一起交流哦。感謝大家的支持,謝謝!

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

推薦閱讀更多精彩內容