初探RxJava以及結合Retrofit的使用

不洗碗工作室發表於2018-02-03

作者:不洗碗工作室--fhyPayaso

轉載請標明出處

前言

android當前最主流的網路請求框架莫過於RxJava2+Retrofit2+OkHttp3,之前的專案中也曾經使用過Retrofit進行網路請求,二次封裝後使用非常方便。但為解決一些巢狀網路請求存在的問題,這次的新專案打算在網路請求中使用RxJava,體驗一下響應式程式設計的感覺。

首先強力推薦扔物線的經典部落格 給 Android 開發者的 RxJava 詳解

觀察者模式

既然要使用RxJava,就不得不簡單介紹一下觀察者模式,因為RxJava作為一個工具庫,使用的就是擴充形式的觀察者模式。

  • 觀察者模式:簡單來說,觀察者模式就是一個物件A (觀察者) 和一個物件B (被觀察者) 達成了一種 訂閱 關係,當物件B觸發了某個事件時,比如一些資料的變化,就會立刻通知物件A,物件A接到通知後作出反應。這樣做的好處就是物件A不用實時監控物件B的狀態,只需在物件B發生特定事件時再作出響應即可。

  • Android中的觀察者模式:舉一個在開發中最常見的觀察者模式的例子OnClickListener

      button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              //點選事件發生後的操作
          }
      });
    複製程式碼

    在這個例子中button是被觀察者,View.OnClickListener是觀察者,而setOnClickListener()方法即讓二者達成了一種訂閱關係,這樣的效果就是:當button觸發了被點選的事件後,會通知觀察者,而OnClickListener接收到通知後,就會呼叫自身介面中的onClick()作為對點選事件的響應。

介紹

首先我們來看一下官方在github上對RxJava的介紹:

RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences.(一個通過使用可觀察序列來組成非同步的、基於事件的程式的庫。)

從介紹中我們可以提取出一個關鍵詞:非同步,但安卓中已經有很多解決非同步操作的方法了,比如HandlerAsyncTask等,為什麼還選擇RxJava呢,其實目的就是為了讓程式碼更簡潔,而且它的簡潔是與眾不同的,因為RxJava的使用方式是基於事件流的鏈式呼叫,這就保證了隨著程式複雜性的提高,RxJava依然能保持程式碼的簡潔和優雅。具體的例子將在後面結合Retrofit展示。

因為RxJava是基於一種擴充的觀察者模式,所以也有觀察者、被觀察者、訂閱、事件幾個基本概念,這裡借用大神Carson_Ho部落格中的例子解釋一下概念和基本原理。

角色 作用 類比
Observable(被觀察者) 事件的產生者 顧客
Observer(觀察者) 事件消費者,接收事件後作出響應 廚師
Subscribe(訂閱) 將Observable和Observer連線在一起 服務員
Event(事件) Observable通知Observer的載體 菜品

初探RxJava以及結合Retrofit的使用

基本實現

  • 依賴新增:開始實踐之前記得在gradle中新增依賴(本文釋出時的RxJava2.0的最新版本)。

      compile "io.reactivex.rxjava2:rxjava:2.1.1"
      compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    複製程式碼
  • Observable(被觀察者) 的建立:

      Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
          @Override
          public void subscribe(ObservableEmitter<String> e) throws Exception {
    
              e.onNext("事件1");
              e.onNext("事件2");
              e.onNext("事件3");
              e.onComplete();
          }
      });
    複製程式碼
    • Observable.create(): 建立一個Observable的最基本方法,也可以通過just()from()等方法來簡化操作。
    • ObservableOnSubscribe<>(): 一個介面,在複寫的subscribe()裡定義需要傳送的事件。
    • ObservableEmitter: 這是RxJava2中新推出的類,可以理解為發射器,用於發射資料onNext()和通知onComplete()/onError()
  • Observer(觀察者) 的建立:

      Observer<String> observer = new Observer<String>() {
    
          @Override
          public void onSubscribe(Disposable d) {
    
              Log.d(TAG, "onSubscribe: 達成訂閱");
          }
    
          @Override
          public void onNext(String s) {
    
              Log.d(TAG, "onNext: 響應了"+s);
          }
    
          @Override
          public void onError(Throwable e) {
          
              Log.d(TAG, "onError: 執行錯誤");
          }
    
          @Override
          public void onComplete() {
      			
              Log.d(TAG, "onComplete: 執行完成");
          }
      };
    複製程式碼
    • onNext():普通事件,通過重寫進行響應即可。
    • onError():錯誤事件,當佇列中事件處理出現異常時,就會呼叫該方法,此後不再有事件發出。
    • onComplete():完成事件,當佇列中的事件全部處理完成後觸發。
    • 在一個正常的序列中,onError()onComplete()應該只有一個並且處於事件佇列的末尾,而且呼叫了一個就不應該再呼叫另一個。

    除了Observer之外,RxJava 還內建了一個實現了Observer的抽象類:Subscriber,兩者的使用方式基本一樣,主要區別在於Subscriber中新增了兩個方法,下面引用一下扔物線對這兩個方法的解釋:

1、onStart(): 這是 Subscriber 增加的方法。它會在 subscribe 剛開始,而事件還未傳送之前被呼叫,可以用於做一些準備工作,例如資料的清零或重置。這是一個可選方法,預設情況下它的實現為空。需要注意的是,如果對準備工作的執行緒有要求(例如彈出一個顯示進度的對話方塊,這必須在主執行緒執行), onStart() 就不適用了,因為它總是在 subscribe 所發生的執行緒被呼叫,而不能指定執行緒。要在指定的執行緒來做準備工作,可以使用 doOnSubscribe() 方法。

2、unsubscribe(): 這是 Subscriber 所實現的另一個介面 Subscription 的方法,用於取消訂閱。在這個方法被呼叫後,Subscriber 將不再接收事件。一般在這個方法呼叫前,可以使用 isUnsubscribed() 先判斷一下狀態。 unsubscribe() 這個方法很重要,因為在 subscribe() 之後, Observable 會持有 Subscriber 的引用,這個引用如果不能及時被釋放,將有記憶體洩露的風險。所以最好保持一個原則:要在不再使用的時候儘快在合適的地方(例如 onPause() onStop() 等方法中)呼叫 unsubscribe() 來解除引用關係,以避免記憶體洩露的發生。

  • 二者達成 subscribe(訂閱) 關係:

      observable.subscribe(observer);
    複製程式碼
  • 鏈式呼叫

    上面的三步過程可以通過一條鏈式結構直接呼叫,使得程式碼變得更簡潔。

      Observable.create(new ObservableOnSubscribe<String>() {
          @Override
          public void subscribe(ObservableEmitter<String> e) throws Exception {
    
              e.onNext("事件1");
              e.onNext("事件2");
              e.onNext("事件3");
              e.onComplete();
          }
      }).subscribe(new Observer<String>() {
          @Override
          public void onSubscribe(Disposable d) {
    
              Log.d(TAG, "onSubscribe: 達成訂閱");
          }
    
          @Override
          public void onNext(String s) {
    
              Log.d(TAG, "onNext: 響應了"+s);
          }
    
          @Override
          public void onError(Throwable e) {
    
              Log.d(TAG, "onError: 執行錯誤");
          }
    
          @Override
          public void onComplete() {
    
              Log.d(TAG, "onComplete: 執行完成");
          }
      });
    複製程式碼

    在logcat中可以檢視列印結果

    初探RxJava以及結合Retrofit的使用

執行緒排程

在RxJava中,我們可以自行指定事件產生和事件消費的執行緒,可以通過RxJava中的Scheduler來實現。

Scheduler

  • RxJava內建的5個Scheduler

    • Schedulers.immediate(): 直接在當前執行緒執行,相當於不指定執行緒。這是預設的 Scheduler,但是為了防止被錯誤使用,在RxJava2中已經被移除了。

    • Schedulers.newThread(): 開啟新執行緒,並在新執行緒執行操作。

    • Schedulers.io(): I/O 操作(讀寫檔案、讀寫資料庫、網路資訊互動等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在於 io() 的內部實現是是用一個無數量上限的執行緒池,可以重用空閒的執行緒,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免建立不必要的執行緒。

    • Schedulers.computation(): 計算所使用的 Scheduler,例如圖形的計算。這個 Scheduler 使用的固定的執行緒池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。

    • Schedulers.trampoline():主要用於延遲工作任務的執行。當我們想在當前執行緒執行一個任務時,但並不是立即,我們可以用.trampoline()將它入隊,trampoline將會處理它的佇列並且按序執行佇列中每一個任務。

  • Android特有的Scheduler

    • AndroidSchedulers.mainThread():指定的操作將在Android的主執行緒中進行,如UI介面的更新操作。

執行緒的控制

  • subscribeOn():指定事件產生的執行緒,例如subscribeOn(Schedulers.io())可以指定被觀察者的網路請求、檔案讀寫等操作放置在io執行緒。
  • observeOn():指定事件消費的執行緒,例如observeOn(AndroidSchedulers.mainThread())指定Subscriber中的方法在主執行緒中執行。

subscribe()之前寫上兩句subscribeOn(Scheduler.io())observeOn(AndroidSchedulers.mainThread())的使用方式非常常見,它適用於多數的 <後臺執行緒取資料,主執行緒顯示> 的程式策略。

結合Retrofit

在很多時候RxJava都會結合Retrofit一起使用,而且隨著程式的複雜度提高,RxJava簡潔的優越性就會漸漸得到體現。下面就舉幾個例子來具體感受一下RxJava操作符的神奇。

1、單獨使用Retrofit

  • 首先建立Service介面,這裡拿一個登入介面來舉例

      public interface LoginService {
      
          @POST("login")
          Call<ApiResponse> login(@Query("phone") String username, @Query("password") String password);
      }
    複製程式碼

    其中ApiResponse是自己定義的統一格式的返回體

  • 構造Retrofit併傳送請求

      public void login() {
      
          Retrofit retrofit = new Retrofit.Builder()
                  .addConverterFactory(GsonConverterFactory.create())
                  .baseUrl(Config.APP_SERVER_BASE_URL)
                  .build();
    
          retrofit.create(LoginService.class)
                  .login(phone,password)
                  .enqueue(new Callback<ApiResponse>() {
                      @Override
                      public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
    
                          //登入成功後的操作
                      }
    
                      @Override
                      public void onFailure(Call<ApiResponse> call, Throwable t) {
    
                          //登入失敗後的操作
                      }
                  });
      }		
    複製程式碼

2、結合RxJava

  • 引入依賴:記得給Retrofit新增對RxJava的適配

      compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    複製程式碼
  • 建立Service介面

      public interface LoginService {
          
          @POST("login")
          Observable<ApiResponse> rxLogin(@Query("phone") String phone, @Query("password") String password);
      }
    複製程式碼
  • 構造Retrofit併傳送請求

      public void rxLogin() {
    
          Retrofit retrofit = new Retrofit.Builder()
                  .addConverterFactory(GsonConverterFactory.create())
                  //新增對RxJava的支援
                  .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                  .baseUrl(Config.APP_SERVER_BASE_URL)
                  .build();
    
          retrofit.create(LoginService.class)
                  .rxLogin(phone,password)
                  .subscribeOn(Schedulers.io()) //io執行緒獲取網路請求
                  .observeOn(AndroidSchedulers.mainThread()) //主執行緒進行資料更新
                  .subscribe(new Observer<ApiResponse>() {
                      @Override
                      public void onSubscribe(Disposable d) {
    
                      }
    
                      @Override
                      public void onNext(ApiResponse apiResponse) {
    
                          //登入成功後的操作
                      }
    
                      @Override
                      public void onError(Throwable e) {
    
                          //登入失敗後的操作
                      }
    
                      @Override
                      public void onComplete() {
    
                      }
                  });
    
      }
    複製程式碼

可能大家看了之後會感覺並沒有什麼卵用,不但沒有簡潔,反而增加了程式碼,但老哥們彆著急,這只是最簡單的情況,隨著情況變得複雜,你就會感受到RxJava的強大之處。

3、flatMap()解決巢狀請求

平時在網路請求的過程中,可能會出現這樣的情況,註冊成功之後直接呼叫登入介面,如果不用RxJava,正常的想法肯定是在retrofit的onResponse回撥裡再巢狀一層網路請求,就像這樣:

//註冊相關方法
private void register() {
    
    retrofit.create(RetrofitService.class)
            .register(phone,password)
            .enqueue(new Callback<ApiResponse>() {
                @Override
                public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {

                    Log.d(TAG, "onResponse: 註冊成功");
                    //註冊成功開始登入請求
                    login();
                }

                @Override
                public void onFailure(Call<ApiResponse> call, Throwable t) {
                    Log.d(TAG, "onFailure: 註冊失敗");
                }
            });
    
    
}

//登入相關方法
private void login() {

    retrofit.create(RetrofitService.class)
            .login(phone,password)
            .enqueue(new Callback<ApiResponse>() {
                @Override
                public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {

                    Log.d(TAG, "onResponse: 登入成功");
                }

                @Override
                public void onFailure(Call<ApiResponse> call, Throwable t) {

                    Log.d(TAG, "onFailure: 登入失敗");
                }
            });
}
複製程式碼

實際上這種很low的巢狀網路請求正是我們需要避免的,而在RxJava中,通過flatMap()操作符可以避免這種巢狀,flatMap的作用是對傳入的上一個資料流中的物件進行處理,返回下一級所要的物件的Observable包裝,相當於將二維的巢狀過程線性化了,先舉個最原始的例子:

private void loginAndRegister() {


	//獲得被觀察者實體
    RetrofitService service = retrofit.create(RetrofitService.class);
    Observable<ApiResponse> register = service.register(phone, password);
    final Observable<ApiResponse> login = service.login(phone, password);


    register.subscribeOn(Schedulers.io()) //(註冊被觀察者)切換到IO執行緒進行註冊網路請求
            .observeOn(AndroidSchedulers.mainThread()) //(註冊觀察者)切換到主執行緒 處理註冊網路請求的結果
            .doOnEach(new Observer<ApiResponse>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(ApiResponse apiResponse) {

                    // 對第註冊成功網路請求返回的結果進行操作
                    Log.d(TAG, "註冊成功");
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {

                }
            })
            //切換到IO執行緒去發起登入網路請求(登入被觀察者經過flatMap變換後也變成了相對於註冊的觀察者,所以用observeOn切換執行緒)
            .observeOn(Schedulers.io()) 
            .flatMap(new Function<ApiResponse, ObservableSource<ApiResponse>>() {
                @Override
                public ObservableSource<ApiResponse> apply(ApiResponse apiResponse) throws Exception {
                    return login;
                }
            })
            //(登入觀察者)切回主執行緒處理回撥
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<ApiResponse>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(ApiResponse apiResponse) {

                    Log.d(TAG, "onNext: 登入成功");
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {

                }
            });
}
複製程式碼

可能有的老哥覺得,這樣寫雖然採用了鏈式呼叫,沒有了巢狀,但是這程式碼也太長了,而且重寫了很多無用的方法,彆著急,下面來個優雅版的

private void loginAndRegister() {

    final RetrofitService service = retrofit.create(RetrofitService.class);

    service.register(phone, password)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext(new Consumer<ApiResponse>() {
                @Override
                public void accept(ApiResponse apiResponse) throws Exception {

                    Log.d(TAG, "註冊成功");
                }
            })
            .observeOn(Schedulers.io())
            .flatMap(new Function<ApiResponse, ObservableSource<ApiResponse>>() {
                @Override
                public ObservableSource<ApiResponse> apply(ApiResponse apiResponse) throws Exception {
                    return service.login(phone, password);
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<ApiResponse>() {
                @Override
                public void accept(ApiResponse apiResponse) throws Exception {

                    Log.d(TAG, "登入成功");
                }
            });
}
複製程式碼

Consumer是簡易版的Observer,他有多重過載,可以自定義你需要處理的資訊,這裡呼叫的是隻接受onNext訊息的方法,他只提供一個回撥介面accept,由於沒有onError和onCompete,無法在接受到onError或者onCompete之後,實現函式回撥,雖然無法回撥,但不代表不接收,他還是會接收到onCompete和onError之後做出預設操作,這裡我還是建議大家自己對Observer再進行一下封裝,使用起來會更方便。

在logcat中可以看到請求成功的列印的日誌

初探RxJava以及結合Retrofit的使用

參考文章:

相關文章