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事件会在输入间隔大于一秒时调用,这样如果用户因正在输入造成的接口调用频繁的问题就解决了。