大話RxJava 的基本及應用

RxJava

RxJavaGithub的wiki的介紹:

RxJavais a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences.

大概意思是RxJavaJava VM上一個使用可觀測序列來組成的一個異步的、基于事件的庫。

從這么一句話,看來有兩個關鍵點: ① 異步 ② 事件 ③可觀測 。 概括性的話總是那么抽象,看了這句話,還是不清楚,RxJava到底是干什么的。一個亙古不變的道理, 實踐出真理。只有你用了,你才知道它是什么。

在我看來,RxJava就是一個異步的擴展的觀察者模式。區別于AsynTask以及Handler的優點在于代碼簡潔,鏈式調用。但是注意啦,我這里說的代碼簡潔,可不是說它的代碼量少,而是因為它邏輯清晰簡潔,當你回顧代碼的時候,可以方便的看出當初寫的邏輯。而不像Handler,要找到發送message的地方,以及接受message的地方, 更有各種縮進讓你一番好找,不方便代碼的回溯。而RxJava的鏈式調用,使整體代碼結構看起來很清晰。

RxJava 的神奇之處不僅在于異步的觀察者模式,更強大的在于它的操作符,它提供了大量的操作符來幫助你更好的解決復雜的邏輯。結合Retrofit可以很方便的嵌套請求網絡,解決類似當你要獲取服務器上面的數據的時候,首先要登錄,而登錄之前要先獲取服務器端的一個認證挑戰字。代碼如下:

InnerServerApi.requestLoginCode(mLoginCodeUrl, mAccount).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
                .flatMap(new Func1<LoginCode, Observable<AAAData>>() {
                    @Override
                    public Observable<AAAData> call(final LoginCode loginCode) {
                        Log.d(TAG, loginCode.getLoginCode() + "HAHAHA");
                        return InnerServerApi.requestLoginData(mAAALoginUrl, loginCode.getLoginCode(), mAccount, mPassword);
                    }
                }).subscribeOn(Schedulers.io()).subscribe(new Subscriber<AAAData>() {
                    @Override
                    public void onCompleted() {
                        Logger.i(TAG, "onCompleted");
                    }
                    
                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "login:onFailure");
                    }
                    
                    @Override
                    public void onNext(AAAData loginData) {
                        Log.d(TAG, "login:onSuccess:");
                    }
                    
                });

我們不說代碼量是否多了,反正邏輯是清晰了,這才是我們追求的。 如果采用AsyncTask避免不了的CallBack嵌套,嵌套到回溯起來,自己一時半會都反應不過來呢。 這就是RxJava迷人地方之一,也是最迷人的操作符。下面,我會講一些比較通用的操作符。更多操作符也可以查看API,里面有圖片為你展示事件序列經過操作符之后的流向。

下面就開始學習基本概念啦。。。


RxJava的四個關鍵

  1. Observable 被觀察者,事件的發出者。
  2. Observer 觀察者,被動觀察事件的發出并做相應的處理。 通常我們都是用SubscriberSubscriber是繼承自Observer的一個類,其實就算你實例化的是一個Observer對象,底層也是用Subscriber包裹了這個Observer對象來實現相應的邏輯的,所以可以直接使用Subscriber
  3. event 事件 被觀察者發出的事件
  4. 訂閱 (Subscribe),觀察者訂閱被觀察者,即觀察者監聽被觀察者的動態,一旦被觀察者發出了什么動作就傳送到了觀察者那里,舉個例子,就像我們關注某個微信公眾號,關注這個動作就相當于 訂閱, 然后微信公眾號有新的消息推送,我們就可以看到。我們就是觀察者,公眾號就是被我們觀察的對象。

通過這四個關鍵,更加說明了它是一個異步的擴展的觀察者模式。

RxJava 的家長里短

RxJava的功能強大在于通過操作符變換,線程調度靈活的實現各種事件序列的處理,我覺得Observable的每一個方法都可以描述成一個操作符,下面就用操作符來說。

RxJava的觀察者模式如圖:

RxJava1.png

簡單使用模板

使用RxJava 基本的步驟是下面三步:

創建被觀察者

Observable 創建一個Observable的方法有很多個。其中一個就是create,有關于更多的創建操作符,創建型操作符 點這個可以了解到。

Observable  observable =  Observable.create(new Observable.OnSubscribe<Integer>() {
          @Override
         public void call(Subscriber<? super Integer> subscriber) {
              try {
                  subscriber.onNext(3);
                  subscriber.onNext(4);
                 subscriber.onCompleted();
              } catch (Exception e) {
                  subscriber.onError(e);
             }
          }
     })

call 方法里面是被觀察者訂閱之后要向觀察者發出的事件。

創建觀察者

Subscriber subscriber = new Subscriber<Integer>() {
            @Override
            public void onCompleted() {

            }

           @Override
           public void onError(Throwable e) {
                Log.i(TAG, e.toString());
                }

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

onCompleted 是整個事件序列結束之后調用的方法。在一個正確運行的事件序列中,onCompleted()onError() 有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()onError() 二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個,在自己創建的Observable里面應該注意控制。
onError 是當事件隊列異常。在事件處理過程中出異常時,onError() 會被觸發,同時隊列自動終止,不允許再有事件發出。
onNext,相當于監聽按鈕點擊事件里面的onClick,即接收到被觀察者發出的事件時,觀察者做處理的函數。

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

訂閱

  //被觀察者,主動去訂閱觀察者,為了實現鏈式調用就設計成觀察者主動訂閱觀察者。
  observeable.subscribe(subscriber);

這樣就完成了一個事件的訂閱的流程。

線程控制Scheduler

在RxJava中我們可以通過Scheduler來實現多線程,指定事件發送的線程,以及事件處理的線程。事件發送就是Observable的call方法執行的代碼,通過subscribeOn我們可以指定call方法執行的線程。事件處理即Subscriber事件處理方法執行的線程,通過observerOn可以指定事件處理的線程。不過調用subscribeOnobserverOn切換線程,沒有再做切換,則此次事件一直處于最后一次切換的線程。

需要注意的是,①observerOn可以多次調用并且有效,但SchedulerOn 雖然也可以多次調用,但是只有第一次是有效的。②默認的Observable的產生事件,以及通知事件給Observer是在同一個線程里面的,這個線程就是subscribe所在的線程。③有些操作符是有默認的調度器的,可以查看官方文檔。比如timer默認就是computation Scheduler.

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。
  • AndroidSchedulers.mainThread(),它指定的操作將在 Android 主線程運行,如果希望Observer的事件處理發生在主線程的話,就要調用observerOn(AndroidSchedulers.mainThread()).

常用的幾個操作符

  • 創建型操作符

from

github介紹:

from() — convert an Iterable, a Future, or an Array into an Observable

from(java.lang.Iterable<? extends T> iterable) /from(T[] array),Observable可用于將傳入的數組或者集合中拆分成具體的對象,分發下去。

 ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(0);
        list.add(4);
        Observable.from(list); //觀察者依次接收到 1,2,0,4.

just

just() — convert an object or several objects into an Observable that emits that object or those objects

just(T t1, T t2··· T tn),其實just與from 差不多,也是將t1, t2··· tn依次分發下去。

Observable.just("Tom" , "John", "Mary");

timer

timer() — create an Observable that emits a single item after a given delay

Paste_Image.png

常用的timer(long delay, java.util.concurrent.TimeUnit unit), delay延遲的時間,unit延遲的時間的單位。用于定時在一定時間之后,再分發事件,觀察者接收到信號,做相應處理。相當于Handler里面的延遲發送。

        Observable.timer(1000, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io())
                .subscribe(new Subscriber<Long>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Long aLong) {
                        mThread = Thread.currentThread();
                        Log.i(TAG, "name2" +mThread.getName() + "pid" + mThread.getId());
                    }
                });

interval

interval( ) — create an Observable that emits a sequence of integers spaced by a given time interval

interval(long interval, java.util.concurrent.TimeUnit unit) ,相當于Timer定時器,定時分發事件。與timer的區別在于可以定時的重復分發事件。而timer只操作一次。repeatWhen跟interval的作用是一樣的。

  • 過濾型操作符

filter

filter() — filter items emitted by an Observable

Paste_Image.png

可以過濾事件,只有符合條件的事件才能被繼續分發下去。

    Observable.just(1,2,0,3).filter(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
            //過濾掉等于0的事件
                if(integer != 0) {
                  return integer;
                }
                return false;
            }
        })

last

last() — emit only the last item emitted by an Observable

只能發整個事件序列的最后一個事件。同理,first(),只分發整個事件序列的第一個事件。

Paste_Image.png
  • 強大的轉換操作符

RxJava中最強大的就是轉換型操作符了,可以對Observable進行一些轉換,做更多的操作,實現嵌套之類的邏輯。在我看來轉換操作符的作用就是將一個Observable轉換成另外一個Observable,我們這里假設只有兩個Observable,當訂閱之后,第一個Observable的call方法首先被調用,即第一個Observable發送事件序列,第二個Observable對第一個Observable發送出來的事件做處理(比如filiter對事件進行過濾)或者對第一個Observable返回的數據類型進行處理轉換成另外一個對象或者Observable(比如Map以及flatMap)然后第二個Observable開始發送事件序列,最后在Subscribe里面進行處理。在官方的API里面有形象的"彈珠圖"來演示事件發送的發展。可以點這里去看 ===> RxJava API

map

map將一個對象轉換成另外一個對象分發下去,返回的是一個Observable對象。

Paste_Image.png
Observable.just(1,2,3,4).map(new Func1<Integer, String>() {
                        @Override
                        public String call(Integer i) {
                            Log.i(TAG, "integer" +i);
                            return s;
                        }
                    });

上述代碼就是將Integer轉換成String對象分發下去。

flatMap

flatMap可以網絡請求的嵌套,比如請求服務器要先申請到挑戰字 再登錄服務器,代碼就可以如下使用:

Paste_Image.png
 InnerServerApi.requestLoginCode(mLoginCodeUrl, getSN()).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
                .flatMap(new Func1<LoginCode, Observable<AAAData>>() {
                    @Override
                    public Observable<AAAData> call(LoginCode loginCode) {
                        Log.d(TAG, loginCode.getLoginCode());
                        String displayId = android.os.Build.DISPLAY;
               return InnerServerApi.requestLoginData(mAAALoginUrl,loginCode.getLoginCode(), "doris", mPassword);
                    }
                    // if error, retry ,3 times.
                }).subscribeOn(Schedulers.io()).retry(3).subscribe(new Subscriber<AAAData>() {
            @Override
            public void onCompleted() {
                Logger.i(TAG, "onCompleted");
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, e.toString());
                Log.i(TAG, "login error");
            }

            @Override
            public void onNext(AAAData loginData) {
                Log.i(TAG, "login Success");
            }

        });

mapflatMap 的區別在于map 是一對一,而且map是直接返回一個對象,而flatMap則是返回Observable,用于分發事件。flatMap的一對多的體現看下面這段代碼:

Observable.from(folders)
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })

這段代碼,先通過第一個Observable把一個個目錄發送出去,然后通過flatMap再把目錄中的文件一個個分發下去。這是map做不到的。

compose

Observable進行一個整體的變化,flatMap只是對接收到的事件一個一個的轉換,而compose是對整個Observable做一些處理。

//自定義一個轉換器
final Observable.Transformer    schedulersTransformer   = new Observable.Transformer() {
                        @Override
                        public Object call(Object observable) {
                            return ((Observable) observable).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io())
                                    .observeOn(AndroidSchedulers.mainThread());
                        }
                    };
                    
//這樣就對observable進行了上面call方法里面的操作。
observable.compose(schedulersTransformer); 
  1. 還有很多zipWithmerge等這些合并Observable,可以看這里Combing操作符
  • 錯誤處理操作符

retry

retry(long count) 常用的retry方法,當發生錯誤時,retry count次,比如我們網絡請求的時候失敗了,我們可以重新請求三次,retry(3).

Paste_Image.png

onErrorResumeNext

當發生錯誤的時候,調用這個操作符里面的方法,將當前的Observable轉換成另一個Observable繼續發送事件。應用場景,當我們請求當前服務器失敗的時候,可以選擇一個備用的服務器地址重新請求數據。

Paste_Image.png

RxJava 與 Retrofit 結合實例

RetrofitRxJava結合可以更好的完成網絡請求。不了解Retrofit的,趕緊去學習吧,Retrofit的底層使用OkHttp完成網絡請求。下面給大家講講,我用到的網絡請求的例子以及踩過的坑。

  • 從服務器獲取文件,失敗的話重試

/**  downloadFile是返回的是一個Observable對象,picUrl是要下載的圖片url,我將這個事件發送在IO線程中執行。
retry(3),如果失敗的話,重試三次,下載文件
map 將得到的數據寫入到文件中。
**/
downloadFile(picurl).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).retry(3).filter(new Func1<String, Boolean>() {
            @Override
            public Boolean call(String picUrl) {
                                //調用filter,過濾掉null的Url,如果picUrl是空的話,那么就不下載
                return !TextUtils.isEmpty(picUrl) && !picUrl.equals("null");
            }
        }).map(new Func1<ResponseBody, String>() {
            @Override
            public String call(ResponseBody responseBody) {
                return writeToFile(responseBody, mFileName);
            }
        }).subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                Logger.i(TAG, "testAAA downloadCompleted");
            }
            
            @Override
            public void onError(Throwable e) {
                Logger.i(TAG, "testAAA downloadFail" + e.toString());
            }
            
            @Override
            public void onNext(String   s) {
                               //返回 下載的文件存放路徑
                Logger.i(TAG, "onNext" + s);
                                
            }
        });

在上述代碼中,通過filter過濾掉了不合理的url,其實也可以添加一個判斷是否是一個Http網址的判斷,你可以自己試試。并且通過map直接將下載到的數據,存儲到了文件當中,而且如果下載過程中出現類似的 socket timeout錯誤,可以通過,retry重新請求,一個簡單的鏈式調用就下載文件并保存的邏輯寫好了。 So easy! 必須給RxJava怒贊!!!

  • 嵌套網絡請求

InnerServerApi.requestLoginCode(mLoginCodeUrl, mAccount).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
                .flatMap(new Func1<LoginCode, Observable<AAAData>>() {
                    @Override
                    public Observable<AAAData> call(final LoginCode loginCode) {
                        Log.d(TAG, loginCode.getLoginCode() + "HAHAHA");
                        return InnerServerApi.requestLoginData(mAAALoginUrl, loginCode.getLoginCode(), mAccount, mPassword);
                    }
                }).subscribeOn(Schedulers.io()).subscribe(new Subscriber<AAAData>() {
                    @Override
                    public void onCompleted() {
                        Logger.i(TAG, "onCompleted");
                    }
                    
                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "login:onFailure");
                    }
                    
                    @Override
                    public void onNext(AAAData loginData) {
                        Log.d(TAG, "login:onSuccess:");
                    }
                    
                });

解釋一下,requestLoginCode是通過Retrofit定義的一個返回Observable對象的一個方法,獲取我要登錄服務器需要用到的 驗證字段, 得到驗證字段之后,通過flatMap直接通過得到的LoginCode調用登錄方法requestLoginDatarequestLoginData返回的也是一個Observable對象,然后訂閱,最后獲取到登錄返回的數據。這樣說可能大家不明白,我說說我這個代碼的應用場景,你要登錄一個服務器,首先你要向他申請一個跟你的賬號相關的 驗證碼,然后再通過驗證碼,以及賬號密碼登錄。 就想你要吃蘋果,那么你要先買個水果刀,然后才能切水果最后吃到水果。 大家可以回想一下,沒有RxJava之前,利用AsyncTask我們都是如何完成這個邏輯的,首先獲取到LoginCode,然后再Callback的onSuccess方法中再調用一個AsyncTask請求,然后就出現了很多迷之縮進,相比之下,RxJava簡直是飛流直下,邏輯明了清晰。不相信的機智的你,可以寫一段對比一下。 我這里就不貼出來的。

  • 錯誤處理 onErrorResumeNext

在應用開發中,會遇到這樣的需求,有幾個備份的網絡地址,當你請求第一個網絡地址不成功的時候,你想要用備份的網絡地址。RxJava可以很方便的,幫你捕捉到錯誤,并且用備份的地址,重新請求。 就獲取網絡時間的代碼,來舉個例子吧。

    Observable.create(new Observable.OnSubscribe<Date>() {
            @Override
            public void call(Subscriber<? super Date> subscriber) {
                try {
                                      //第一次從百度上面獲取網絡時間
                    mTimeUrl = "http://www.baidu.com/";
                    Logger.d(TAG, "mTimeUrl" + mTimeUrl);
                    Date date = GetNetworkTime.getWebsiteDate();
                    Logger.d(TAG, "call date" + date);
                    subscriber.onNext(date);
                    subscriber.onCompleted();
                } catch (IOException e) {
                                      //如果網絡超時則調用onError,觸發onErrorResumeNext通過備份的地址 http://www.beijing-time.org 獲取網絡時間。
                    subscriber.onError(e);
                    e.printStackTrace();
                }
            }
        }).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).onErrorResumeNext(new Func1<Throwable, Observable<? extends Date>>() {
            @Override
            public Observable<? extends Date> call(Throwable throwable) {
                return Observable.create(new Observable.OnSubscribe<Date>() {
                    @Override
                    public void call(Subscriber<? super Date> subscriber) {
//通過http://www.beijing-time.org 請求時間,如果這次訪問失敗,則獲取網絡時間失敗 
                        mTimeUrl = "http://www.beijing-time.org";
                        try {
                            Date date = GetNetworkTime.getWebsiteDate();
                            Logger.d(TAG, "call1" + date);
                            subscriber.onNext(date);
                            subscriber.onCompleted();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                    }
                });
            }
        }).subscribe(new Observer<Date>() {
            @Override
            public void onCompleted() {
                Logger.d(TAG, "onCompleted");
            }
            
            @Override
            public void onError(Throwable e) {
            }
            
            @Override
            public void onNext(Date date) {
                Logger.d(TAG, "call3" + date);
                netWorkCallback.doSometing(date);
            }
        });

在上述代碼中,當第一個Observable發送獲取網絡時間的時候超時的時候,我們自己調用OnError方法,從而onErrorResumeNext攔截到錯誤并且將當前的Observable轉換成另外一個Observable,通過這個新的Observable繼續發送新的事件,這里的新的事件就是通過備份的url,再次獲取網絡是事件。這個就可以完成多個地址切換請求網絡數據,當第一個地址不成功,換第二個地址。這在做應用開發時也經常用到。我覺得挺實用的,當然onErrorResumeNext的應用場景還有很多,就等你慢慢發掘吧。

總結

  1. 在用RxJava的時候,要注意當前的操作是哪個線程。注意不要把應該在UI線程操作的放在了子線程,也不要把大量的操作放在主線程。

  2. Observable 發送一串事件序列的時候,如果其中有一個出錯了,那么接下來的事件,觀察者都不會接收到。即 OnError是整個事件結束的標志,如果出錯了,并且沒有做什么出錯處理,那么就直接調用了OnError,結束整個事件。 所以這意味著,你利用RxJava發送了一個下載十個文件的事件序列時,如果其中有一個文件下載失敗,其他的文件就停止下載了。這個時候你只能用,for循環,然后一個一個下載,方便控制。

參考鏈接

  1. 給 Android 開發者的 RxJava 詳解

  2. 官方文檔

  3. RxJava API

最后的最后,我說的哪里不對的或者有什么問題,歡迎留言,大家共進步。O

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容