RxJava现学现用下(应用场景)

目录

前言

使用场景

场景1:interval周期性任务

场景2:网络嵌套请求

场景3:合并数据源展示

场景4:缓存处理

场景5:联合判断

场景6:重试机制

场景7:防抖机制

场景8:联想搜索

场景9:背压

场景10:结合其他框架一起使用

总结

 


前言

之前两篇文章介绍了RxJava常用的操作符,本文将从实际开发着手,来聊聊RxJava的应用场景。再聊这个之前,先回顾一下RxJava的操作符:

如上图所示,RxJava基本分为这6类操作符,下面针对这6类操作符,具体介绍一下应用场景。


使用场景

场景1:interval周期性任务

适用于每隔一段时间执行某项任务,比如轮询请求,如果执行指定数量的次数可以用intervalRange,一次可以用timer。

        // interval() 隔一段事件发送事件 从0开始无限+1
        // 第1个参数第一次延迟时间
        // 第2个参数 间隔时间
        // 第3个参数 时间单位
        Observable.interval(2,1,TimeUnit.SECONDS)
                .subscribe(new Observer<Long>() {
                    // do something
                });

场景2:网络嵌套请求

我们在平时开发过程应该遇到过,就是一个请求依赖上个请求的结果,对于这种嵌套请求,如果我们不用RxJava,往往代码写的不够优雅。

// 假设一个需求,一个新用户注册账号,注册成功后直接登陆账号,注册失败提示用户

// 一般的写法(伪代码)

// register方法
private void register(){
  api.register(registerBean)
     .subscribeOn(Schedulers.io())               // 在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  // 回到主线程去处理请求结果
                .subscribe(new Consumer<RegisterResponse>() {
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        login();   // 注册成功, 调用登录的方法
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        // 提示注册失败
                    }
                });
     
}

private void login(){
   api.login(LoginBean)
      .subscribeOn(Schedulers.io())               // 在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  // 回到主线程去处理请求结果
                .subscribe(new Consumer<LoginResponse>() {
                    @Override
                    public void accept(LoginResponse response) throws Exception {
                        // 登陆成功,跳转主页面
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        // 提示登陆失败
                    }
                });
     
}

如果我们用RxJava实现如下:

 // FlatMap 将被观察者发送的事件,经过合并、拆分或者单独转换生成一个新的事件序列再发送
 // 新合成的事件和原事件发送顺序无关
 // 伪代码
 Observable<RegisterBean> observable1 = Api.register();
 Observable<LoginBean> observable2 = Api.login();

 
 observable1.subscribeOn(Schedulers.io()) // io线程请求网络
            .observeOn(AndroidSchedulers.mainThread()) // 请求完成回调主线程查看结果
            .subscribe(new Consumer<RegisterResponse>() {
            @Override
            public void accept(RegisterResponse response) throws Exception {
               // 注册成功
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
               // 注册失败
            }
        }).observeOn(Schedulers.io()) // 新被观察者切换io线程请求网络              
           // 作变换操作将注册请求转成登陆请求                                                
          .flatMap(new Function<RegisterBean, ObservableSource<LoginBean>>() { 
                        @Override
                        public ObservableSource<LoginBean> apply(RegisterBean result) throws Exception {
                          
                            return observable2;
                        }
                    })
                    // 请求结果切换到主线程展示
                    .observeOn(AndroidSchedulers.mainThread()) 
                    .subscribe(new Consumer<Translation2>() {
                        @Override
                        public void accept(Translation2 result) throws Exception {
                          // 登陆成功
                         
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                          // 登陆失败
                        }
                    });

我们从上面代码,可以看出,如果是嵌套请求,RxJava将第一个请求直接变换成第二个请求,从上到下,成功失败是不是相比之前的嵌套回调,要更一目了然。

场景3:合并数据源展示

例如我们展示数据时,需要用到两组数据展示,本地+网络合并显示,使用merge操作符:

        // 网络获取
        Observable<String> network = Observable.just("网络");
        // 本地获取
        Observable<String> file = Observable.just("本地文件");

        Observable.merge(network, file)
                .subscribe(new Observer<String>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(String value) {
                      // 接收数据
                    }

                    @Override
                    public void onError(Throwable e) {
                       
                    }

                    
                    @Override
                    public void onComplete() {
                      // 合并数据统一展示
                    }
                });

场景4:缓存处理

我们经常在开发中会遇到一个问题,就是我们获取数据时,一般先从内存缓存获取,再从磁盘缓存处理,最后在执行网络请求获取,这里我们用到concat+firstElement(第一个有效数据)操作符。

        Observable<String> memory = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Exception {
                if (memoryCache != null) {
                    emitter.onNext(memoryCache);
                } else {
                    emitter.onComplete();
                }
            }
        });


        Observable<String> disk = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Exception {
                if (diskCache != null) {
                    emitter.onNext(diskCache);
                } else {
                    emitter.onComplete();
                }
            }
        });
 
        Observable<String> network = Observable.just("network");
 
        // 依次检查memory、disk、network
        Observable.concat(memory, disk, network)
            .firstElement()
            .subscribeOn(Schedulers.newThread())
            .subscribe({
             // 结果展示
         });

场景5:联合判断

适用于我们有个表格多个数据需要输入,才能提交,使用CombineLatest操作符。

// 跳过第一次输入
        Observable<CharSequence> nameObservable = RxTextView.textChanges(name).skip(1);
        Observable<CharSequence> ageObservable = RxTextView.textChanges(age).skip(1);
        Observable<CharSequence> idObservable = RxTextView.textChanges(id).skip(1);

        Observable.combineLatest(nameObservable, ageObservable, idObservable, new Function3<CharSequence, CharSequence, CharSequence, Boolean>() {
            @NonNull
            @Override
            public Boolean apply(@NonNull CharSequence charSequence, @NonNull CharSequence charSequence2, @NonNull CharSequence charSequence3) throws Exception {
                // 1. 姓名信息
                boolean isUserNameValid = !TextUtils.isEmpty(name.getText());
                // 2. 年龄信息
                boolean isUserAgeValid = !TextUtils.isEmpty(age.getText());
                // 3. 身份证信息
                boolean isUserIdValid = !TextUtils.isEmpty(id.getText());

                return isUserNameValid && isUserAgeValid && isUserIdValid;
            }
        }).subscribe(new Consumer<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) throws Exception {
                // 最后判断是否可以提交
            }
        });

场景6:重试机制

例如我们执行某个请求或操作时,需要在一定的时间重试几次,最后再返回结果,这里用到repeatWhen操作符。

// 模拟网络请求或其他操作
Observable networkObservable = Observable.just("network") ;
networkObservable
                .repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Observable<Object> objectObservable) throws Exception {
                        return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {

                            @Override
                            public ObservableSource<?> apply(Object o) throws Exception {
//                                return Observable.empty(); // 发送complete事件 但观察者onComplete不会回调
//                                return Observable.error(new Throwable("出错了!!!"));
//                                return Observable.just(1);

                                // 加入判断条件:当轮询次数 = 4次后,就停止轮询
                                if (i > 2) {
                                    // 此处选择发送onError事件以结束轮询,因为可触发下游观察者的onError()方法回调
                                    return Observable.error(new Throwable("轮询结束"));
                                }

                                // 注:此处加入了delay操作符,作用 = 延迟一段时间发送(此处设置 = 1s),以实现轮询间间隔设置
                                return Observable.just(1).delay(1000, TimeUnit.MILLISECONDS);
                            }
                        });
                    }
                }).subscribe(new Observer<Integer>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

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

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "收到onError事件: " + e.toString());
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "收到onComplete事件");
            }
        });

从上面代码可以看出,repeatWhen相当于有条件重试,我们可以根据实际需求改成对应的重试条件和重试间隔。

场景7:防抖机制

例如我们按钮点击的时候经常会遇到,短时间重复点击的问题,这里用到throttleFirst操作符,一段时间内只响应第一次事件。

        RxView.clicks(btn)
                .throttleFirst(1,TimeUnit.SECONDS)
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                       // 处理点击事件
                    }
                });

场景8:联想搜索

例如我们在输入框输入文字时,联想搜索,为了减少不必要的网络请求,我们会在一段时间间隔过滤事件,这里用到debounce操作符。

        RxTextView
                .textChanges(name)
                // skip(1)跳过第一次数据为空事件,debounce 1秒中只执行一次
                .debounce(1,TimeUnit.SECONDS).skip(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<CharSequence>() {
                    @Override
                    public void accept(CharSequence charSequence) throws Exception {
                        // 服务器请求的数据
                    }
                });

上面的代码,简单来说就是,除去第一次输入框文字为空,输入框文字1秒钟没有发生变化才请求网络搜索。

场景9:背压

先说说背压是什么吧:

背压:被观察者发送事件速度和观察者接收事件速度不匹配,观察者内会创建一个无限制大小的缓冲池存储未接收的事件,存储事件越来越多时造成OOM的现象,称为背压。(背压通常发生在被观察者和观察者异步订阅即subscribeOn与observeOn不为同一个线程时,被观察者与观察者内存在不同时长耗时任务)

为了解决这种情况的策略称为背压策略(Flowable)。

先介绍一下背压策略种类(BackpressureStrategy):

  • ERROR:当被观察者发送事件大于128(Flowable默认缓存是128)时,观察者抛出异常并终止接收事件,但不会影响被观察者继续发送事件。
  • BUFFER:与Observable一样存在背压问题,但是接收性能比Observable低,因为BUFFER类型通过BufferAsyncEmitter添加了额外的逻辑处理,再发送至观察者。
  • DROP:每当观察者接收128事件之后,就会丢弃部分事件。
  • LATEST:LATEST与DROP使用效果一样,但LATEST会保证能接收最后一个事件,而DROP则不会保证。
  • MISSING:MISSING就是没有采取背压策略的类型,效果跟Obserable一样。

介绍策略我们看下出现背压问题和解决的代码:

 // 模拟出现背压情况
 Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                int i = 0;
                while (true) {
                    Thread.sleep(500);
                    i++;
                    e.onNext(i);
                    Log.i(TAG, "每500ms发送一次数据:" + i);
                }
            }
        }).subscribeOn(Schedulers.newThread())// 使被观察者存在独立的线程执行
                .observeOn(Schedulers.newThread())// 使观察者存在独立的线程执行
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer integer) throws Exception {
                        Thread.sleep(5000);
                        Log.e(TAG, "每5000ms接收一次数据:" + integer);
                    }
                });

// 使用Flowable BackpressureStrategy.ERROR策略解决背压问题
Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                for (int j = 0; j <= 150; j++) {
                    e.onNext(j);
                    Log.i(TAG, " 发送数据:" + j);
                    try {
                        Thread.sleep(50);
                    } catch (Exception ex) {
                    }
                }
            }
        }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE); // 观察者设置接收事件的数量,如果不设置接收不到事件
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.e(TAG, "onNext : " + (integer));
                    }

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

                    @Override
                    public void onComplete() {
                        Log.e(TAG, "onComplete");
                    }
                });

从上面代码代码可以看出Flowable和Observable差不多,主要是创建增加了背压策略参数,被观察者、观察者实现类有所区别。

上面介绍背压策略MISSING是没有采取背压策略但是结合onBackPressure操作符达到同样的效果:

 Flowable.interval(50,TimeUnit.MILLISECONDS)
        .onBackpressureDrop() // 效果与Drop类型一样
        .subscribeOn(Schedulers.newThread())
        .observeOn(Schedulers.newThread())
        .subscribe(new Consumer<Long>() {
            @Override
            public void accept(Long aLong) throws Exception {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e(TAG,"onNext : "+(aLong));
            }
        });

onBackPressure操作符主要包括以下三种类型:

  • onBackpressureBuffer :与BUFFER类型一样效果。
  • onBackpressureDrop :与DROP类型一样效果。
  • onBackpressureLaster :与LASTER类型一样效果。

背压策略说完了,最后讲讲request(long count)、requested(long count)这两个方法,前者最大接收数量、后者剩余可接收数量:

Flowable.create(new FlowableOnSubscribe<Integer>() {
            @Override
            public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                for(int j = 0;j<15;j++){
                    e.onNext(j);
                    Log.i(TAG,e.requested()+" 发送数据:"+j); // 剩余可接收数量
                    try{
                        Thread.sleep(50);
                    }catch (Exception ex){
                    }
                }
            }
        },BackpressureStrategy.BUFFER)
//        .subscribeOn(Schedulers.newThread())
//        .observeOn(Schedulers.newThread())
        .subscribe(new Subscriber<Integer>() {
            private Subscription subscription;
            @Override
            public void onSubscribe(Subscription s) {
                subscription = s;
                s.request(10); // 观察者设置接收事件的数量,如果不设置接收不到事件
            }
            @Override
            public void onNext(Integer integer) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e(TAG,"onNext : "+(integer));
            }
            @Override
            public void onError(Throwable t) {
                Log.e(TAG,"onError : "+t.toString());
            }
            @Override
            public void onComplete() {
                Log.e(TAG,"onComplete");
            }
        });

背压相关策略、操作符、方法大概介绍完了,如果想深入了解背压原理,可以看下这篇文章:Android RxJava :图文详解 背压策略

场景10:结合其他框架一起使用

RxJava结合其他框架使用大概有以下几种:

  • RxJava+Retrofit实现网络请求
  • Rxbinding 控件的点击、长按、EditText输入文本变化等监听处理
  • Rxbus 和EventBus类似组件通信库,基于RxJava Subject同时承担订阅者、被订阅者角色实现的

第一个就不介绍了,我们在开发中应该经常遇到,主要介绍一下后面两个。

RxBinding我们在之前已经介绍过结合RxJava操作符实现防抖、联合判断、联想搜索功能,看下长按事件:

   RxView.longClicks(mLoginButton)
                .subscribe(new Consumer<Unit>() {
                    @Override
                    public void accept(Unit unit) throws Exception {
                        Log.i(TAG, "accept: longClicks-test");
                    }
                });

最后说一下Rxbus,看下代码:

// 注册消息
  registerRxBus(RxTestBean.class, bean -> {
            if (bean != null) {
                textView.setText(bean.getName() + "-" + bean.getAge());
            }
        });

 // 注册
    public <T> void registerRxBus(Class<T> eventType, Consumer<T> action) {
        Disposable disposable = rxBus.doSubscribe(eventType, action, throwable -> Log.e("rxbus", throwable.toString()));
        rxBus.addSubscription(this, disposable);
    }


// ondestroy解绑
   RxBus.getIntance().unSubscribe(this);

// 发送消息
RxBus.getIntance().post(new RxTestBean("张三", 28))

从上面代码可以看出,和EventBus用法基本类似。


总结

最后总结一下,RxJava在我们平常开发场景还挺多的,当然这些场景不用RxJava也可以实现,但是RxJava实现的方式相比传统的方式要优秀不少,而且多一种解决方案,不相当于手里多一张牌吗,其实这些应用场景也不用死记硬背,有个印象,真正遇到问题时,回顾一下,如果能够解决你遇到的问题不是挺好的吗。最后一张图回顾一下:

Thanks:

RxJava系列教程

关于Rxjava2下的RxBus实现

代码地址:RxJavaLearn

创作不易,如果觉得有用的话,麻烦点个赞。

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值