RxJava操作符(RxBinding與flatMap)的應用場景及其使用細節

語言: CN / TW / HK

RxJava 系列(三):RxJava操作符(RxBinding與flatMap)的應用場景及其使用細節

本文概述:

  • 什麼是"抖動",如何避免抖動;在使用RxJava處理多層巢狀資料的時如何解決程式碼冗餘問題;
  • 本文為RxJava系列文章第三篇,介紹了RxBinding操作符以及flatMap 操作符基本使用,操作細節,底層原理
  • 往期精彩請查閱個人開源框架專欄:https://juejin.cn/column/7112094573395968036

抖動現象:

  • 在一秒中點選按鈕20次,造成伺服器響應20次,這個就是抖動
  • 抖動會徒增伺服器壓力,消耗不必要的資源

採用RxBinding防抖:

  • 新增相關依賴

    implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' // 操作功能防抖

  • 定位需要防止抖動的控制元件:能點選都是可以用的

    // 對那個控制元件防抖動?  Button bt_anti_shake = findViewById(R.id.bt_anti_shake);

  • 新增RXBinding程式碼

    //使用RxBinding  RxView.clicks(bt_anti_shake)

    • 細節:RxBinding可以防止任何控制元件的抖動(入參為View)

      圖片.png

    • 細節:new Consumer儲存事件的泛型是寫死了

      圖片.png

      但是map的話就是沒有寫死的,返回傳入的事件型別

      圖片.png

    • 細節:程式碼報黃該怎麼辦(新增一個註解就行了)

      //這樣兩層巢狀還是可以,再多了就太麻煩了  @SuppressLint("CheckResult")  private void antiShakeActon() {   ……  }

  • 防抖完整程式碼:

    //這樣兩層巢狀還是可以,再多了就太麻煩了  @SuppressLint("CheckResult")  private void antiShakeActon() {      // 注意:(專案分類)查詢的id,通過此id再去查詢(專案列表資料)  ​      // 對那個控制元件防抖動?      Button bt_anti_shake = findViewById(R.id.bt_anti_shake);  ​      //使用RxBinding      RxView.clicks(bt_anti_shake)          // 2秒鐘之內 響應你一次         .throttleFirst(2000, TimeUnit.MILLISECONDS)          //為什麼這裡接收的是Object,因為原始碼已經寫死了         .subscribe(new Consumer<Object>() {              @Override              //這個 o 指代的就是需要防抖的控制元件              public void accept(Object o) throws Exception {                  // 查詢總資料                  api.getProject()                      //呼叫封裝好的執行緒庫,給上面分配非同步執行緒,給下面分配主執行緒                     .compose(DownloadActivity.rxud())                      //ProjectBean:此時的事件是主資料的JavaBean                     .subscribe(new Consumer<ProjectBean>() {                          @Override                          public void accept(ProjectBean projectBean) throws Exception {                              //一個id就是一個Data(指代的是總資料的id)                              for (ProjectBean.DataBean dataBean : projectBean.getData()) { // 10                                  // 查詢item資料:getId指代item資料的id(根據主資料的id,拿到item的id)                                  api.getProjectItem(1, dataBean.getId())                                      //呼叫封裝好的執行緒庫,給上面分配非同步執行緒,給下面分配主執行緒                                     .compose(DownloadActivity.rxud())                                     .subscribe(new Consumer<ProjectItem>() {                                          @Override                                          public void accept(ProjectItem projectItem) throws Exception {                                              // 此時是可以UI操作                                              Log.d(TAG, "accept: " + projectItem);                                         }                                     });                             }                         }                     });             }         });  }

  • 對防抖程式碼進行初始化:不初始化是無法定位哪一個控制元件需要防抖

    @Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      setContentView(R.layout.activity_use);  ​      //API初始化:Retrofit初始化      api = HttpUtil.getOnlineCookieRetrofit().create(WangAndroidApi.class);  ​      // 功能防抖 + 網路巢狀      // antiShakeActon();      antiShakeActonUpdate();  }

    • 此時Button的任何事件都交給了防抖函式進行處理
  • 執行結果:防抖操作的必要性

    圖片.png

  • 但是現在有一個很嚴重的問題:巢狀太深導致程式碼結構不美觀,閱讀體驗極差

    • 引入新的Rx 操作符:flatMap

解決巢狀問題:flatMap

整體描述:

  • 一句話描述:你給它一個數據,它還給你多個數據

  • 示意圖:

    圖片.png

  • 舉例說明:flatMap不僅可以傳遞資料,其還具有額外的分發能力

    圖片.png

編寫具體程式碼:

  • 業務需求:通過RxBinding解決抖動問題,通過flatMap卡片解決前者帶來的巢狀問題

  • 指定需要防抖的控制元件:

    // 對那個控制元件防抖動?  Button bt_anti_shake = findViewById(R.id.bt_anti_shake);

  • 使用RxBinding操作符處理抖動:設定2秒鐘之內只響應一次

    // 2秒鐘之內 響應你一次  RxView.clicks(bt_anti_shake)                 .throttleFirst(2000, TimeUnit.MILLISECONDS)

  • 為下面分配非同步執行緒

    // 我只給下面 切換 非同步  .observeOn(Schedulers.io())

  • 新增flatMap卡片

    //使用flatMap卡片  .flatMap(new Function<Object, ObservableSource<?>>() {  })

    • 細節:為什麼new Function的第一個泛型引數是Object

      • 因為此時事件的起點在RxView.clicks(bt_anti_shake)這個地方,而RxView.clicks裡面的事件型別就是Object是寫死了的

      圖片.png

    • 細節:為什麼第二個泛型引數的型別預設是 ObservableSource<?>

      • 通過這個包裝類,就可以實現flatMap雖然只接受一條資料,但是其可以向後分發多條資料
      • 可以對比map卡片,這個東西的第二泛型引數就是一個基本型別而已
      • 這個裡面的問號實際上是一個萬用字元,但是根據業務需要並不需要其通配,僅指代總資料的 JavaBean即可
      • 這個萬用字元是可以避免的,使用Lamda表示式就行了,但是,使用Lamda表示式後會導致程式碼的可讀性下降
    • flatMap 改造後的程式碼

      .flatMap(new Function<Object, ObservableSource<ProjectBean>>() {      @Override      //返回的是主資料的 JavaBean      public ObservableSource<ProjectBean> apply(Object o) throws Exception {          return api.getProject(); // 主資料     }  })

    • 還有個問題:如何程式碼寫成這個樣子會不會報錯?

      @SuppressLint("CheckResult")  private void antiShakeActonUpdate() {      // 注意:專案分類查詢的id,通過此id再去查詢(專案列表資料)  ​      // 對那個控制元件防抖動?      Button bt_anti_shake = findViewById(R.id.bt_anti_shake);  ​      RxView.clicks(bt_anti_shake)         .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒鐘之內 響應你一次          //使用flatMap卡片         .flatMap(new Function<Object, ObservableSource<ProjectBean>>() {              @Override              //返回的是主資料的 JavaBean              public ObservableSource<ProjectBean> apply(Object o) throws Exception {                  return api.getProject(); // 主資料             }         })  ​         .subscribe(new Consumer<ProjectItem>() {              @Override              public void accept(ProjectItem projectItem) throws Exception {                  // 如果我要更新UI 會報錯2 不會報錯1                  Log.d(TAG, "accept: " + projectItem);             }         });  }

      • 肯定是會報錯的:這樣寫--->使用主執行緒請求伺服器了

        • 防抖是執行在主執行緒的,而這個flatMap 卡片應執行在非同步執行緒;但是,在這個程式碼裡面並無執行緒的切換
    • 有個比較吊的切換執行緒的方式:只給下面切換非同步執行緒

      @SuppressLint("CheckResult")  private void antiShakeActonUpdate() {      // 注意:專案分類查詢的id,通過此id再去查詢(專案列表資料)  ​      // 對那個控制元件防抖動?      Button bt_anti_shake = findViewById(R.id.bt_anti_shake);  ​      RxView.clicks(bt_anti_shake)         .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒鐘之內 響應你一次  ​          // 我只給下面 切換 非同步         .observeOn(Schedulers.io())          //使用flatMap卡片         .flatMap(new Function<Object, ObservableSource<ProjectBean>>() {              @Override              //返回的是主資料的 JavaBean              public ObservableSource<ProjectBean> apply(Object o) throws Exception {                  return api.getProject(); // 主資料             }         })  ​         .subscribe(new Consumer<ProjectItem>() {              @Override              public void accept(ProjectItem projectItem) throws Exception {                  // 如果我要更新UI 會報錯2 不會報錯1                  Log.d(TAG, "accept: " + projectItem);             }         });  }

      • 為什麼.observeOn(Schedulers.io())可以切換執行緒(這個是原始碼裡面的東西)
  • 實現flatMap 向後分發多個數據的功能:此時分發的是總資料

    //現在要開始向後分發多個數據  .flatMap(new Function<ProjectBean, ObservableSource<ProjectBean.DataBean>>() {      @Override      public ObservableSource<ProjectBean.DataBean> apply(ProjectBean projectBean) throws Exception {          // 我自己搞一個發射器 發多次 10次(內部接收一個類似迭代器的東西--->這個引數跟上面的for迴圈一個效果,projectBean內部有10個數據,那麼就分發10次)          return Observable.fromIterable(projectBean.getData());     }  })s

  • 實現分發item資料

    //開始查詢item資料  .flatMap(new Function<ProjectBean.DataBean, ObservableSource<ProjectItem>>() {      @Override      public ObservableSource<ProjectItem> apply(ProjectBean.DataBean dataBean) throws Exception {          //查詢第一頁資料          return api.getProjectItem(1, dataBean.getId());     }  })

  • 此時抵達事件分發終點:

    • 首先切換成主執行緒:flatMap 在非同步執行緒

      // 給下面切換 主執行緒  .observeOn(AndroidSchedulers.mainThread())

    • 展示資料,此時,是可以更新 UI 的

      .subscribe(new Consumer<ProjectItem>() {      @Override      public void accept(ProjectItem projectItem) throws Exception {          // 如果我要更新UI 會報錯2 不會報錯1          Log.d(TAG, "accept: " + projectItem);     }  });

    • 其實,是可以不使用flatMap的,替換成map也是可以的(注意事件的包裝,此時的事件型別應該是包裝類:ObservableSource<具體的JavaBean>)