RxJava操作符怎么用?结合实际的应用场景来谈谈

1,435 阅读4分钟
原文链接: zhuanlan.zhihu.com

RxJava中包括丰富的操作符,本文结合实际的场景来谈谈Rx中常用的操作符的使用。

我们在项目中使用最多的就是Retrofit配合RxJava的网络请求处理。但是项目中可能会有一些特殊的需求或设计不合理的接口,需要客户端对接口的调用进行高精度的控制,这时候RxJava的操作符就派上大用场了。

场景一:使用flatMap注册成功后自动登录

假设现在有一个这样的需求:服务端有注册和登录两个接口,但是这两个接口的功能是分离的,注册成功并不会自动登录,需要客户端再进行调用登录接口才可以。

现在我们来分析下这个场景:需要在注册成功后调用登录,在没有使用RxJava的时候可能会这样解决:在注册登录的回调里再写一个登录,我来用伪代码来模拟下:

Api.register(username, password, new CallBack() {
  public void onSuccess(){
    Api.login(username, password, new CallBack() {
      public void onSuccess() {
        //登陆成功
      }
    });
  }
});

这里没有涉及到异常情况的处理,这样是可以实现这样的功能,但是很容易陷入缩进地狱,假设现在有需要在登录前通过网络获取一下ip,那就是又要加一层回调。这样代码不美观且不利于维护,我们来看看用Rx是怎样处理的吧:

Api.register(account, password)
                .flatMap(new Function<BizResponse<UserInfo>, ObservableSource<UserLoginResponse>>() {
                    @Override
                    public ObservableSource<UserLoginResponse> apply(@NonNull BizResponse<UserInfo> response) throws Exception {
                        // 注册成功后执行登录方法
                        return Api.login(account, password);
                    }
                })
                .subscribe(new Consumer<UserLoginResponse>() {
                    @Override
                    public void accept(@NonNull UserLoginResponse userLoginResponse) throws Exception {
                      // 成功
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        // 失败
                    }
                });

这里我们用到了一个操作符flatMap,它和map很像,不一样的是map从一个对象转化为另一个对象,而flatMap可以从一个对象转化为一个Observable对象,后续继续对它进行操作。

这样咋一看麻烦了很多,其实分析一下其实这段代码更容易维护——如果需要增加新的操作,比如异步获取ip,就在flatMap之前加入这个操作,flatMap之后的代码不用动;而且有统一的异常入口——里面任何的异常都会在第二个Consumer中捕获。

场景二:使用compose简化代码

在网络请求中一般会加入这两句话

observable.subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread());

在io线程执行网络操作,在主线程执行ui操作。但是每个observable都要写这两句话,能不能简化呢?当然是可以的。这就要用到compose操作符。我们可以在RxUtils中定义一个这样的方法:

/**
 * io线程执行,主线程观察
 * .compose(RxUtils.<T>applySchedulers())
 */
public static <T> ObservableTransformer<T, T> applySchedulers() {
    return new ObservableTransformer<T, T>() {
        @Override
        public ObservableSource<T> apply(@NonNull Observable<T> observable) {
            return observable.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread());
        }
    };
}

使用的时候在subscribe方法之前加入.compose(RxUtils.<T>applySchedulers())就可以了。

当然,这个方法可以增加更多的逻辑,比如判断一个接口返回的数据是否异常,一般服务端会返回一个状态来判断:

  /**
     * 增加了统一的判断逻辑
     */
    public static <T extends BizResponse> ObservableTransformer<T, T> applyBizSchedulers() {
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(@NonNull Observable<T> observable) {
                return observable
                        .map(new Function<T, T>() {
                            @Override
                            public T apply(@NonNull final T t) throws Exception {
                                if (t.getRcode() != 0) {
                                    throw new RuntimeException(t.getRmsg());
                                }
                                return t;
                            }
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }

上面的例子中,如果返回的code不是0,则直接抛出异常,统一在 Consumer<Throwable>中处理。

场景三:使用zip对并发的请求做处理

现在有一个这样的需求:假设在用户页面,不仅要显示用户的普通信息,还要显示用户的资金信息。这两个信息分别对应的两个接口,同时访问两个接口,当两个信息都获取成功的时候才算成功,如果有一个没获取成功则算失败,需要重新获取。

如果没有rx,这个需求实现起来非常复杂,只能按照先后顺序获取,这样就会增加加载的时间。

我们可以通过RxJava中的zip来实现:

Observable.zip(Api.getNewsList(), Api.getBanner(), new BiFunction<List<News>, BizResponse<List<Banner>>, Pair<List<News>, List<Banner>>>() {
            @Override
            public Pair<List<News>, List<Banner>> apply(List<News> news, List<Banner> banner) throws Exception {
                return Pair.create(news, banner);
            }
        })
                .compose(RxUtils.applySchedulers())
                .subscribe(...)

场景四:debounce控制频率

假设有一个搜索框,在输入后自动搜索输入的内容。咋一看这个需求很简单,只需要监听EditText的输入事件,然后调用搜索接口就可以。但是如果用户输入过快,就会造成接口的频繁调用。有什么好的方式控制接口的调用频率呢?debounce就可以解决。

RxTextView.textChanges(search)
        .debounce(1, TimeUnit.SECONDS)

通过这段代码,EditText的textChange事件会在输入间隔大于一秒时调用,这样如果用户因正在输入造成的接口调用频繁的问题就解决了。