RxJava 系列(三):RxJava操作符(RxBinding与flatMap)的应用场景及其使用细节

1,081 阅读6分钟

RxJava 系列(三):RxJava操作符(RxBinding与flatMap)的应用场景及其使用细节

本文概述:

  • 什么是"抖动",如何避免抖动;在使用RxJava处理多层嵌套数据的时如何解决代码冗余问题;
  • 本文为RxJava系列文章第三篇,介绍了RxBinding操作符以及flatMap 操作符基本使用,操作细节,底层原理
  • 往期精彩请查阅个人开源框架专栏:juejin.cn/column/7112…

抖动现象:

  • 在一秒中点击按钮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<param1,param2>的第一个泛型参数是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>)