RxJava配合Retrofit的一些场景
场景一:retry request
-
原始方案:
private void makeRequest(){ getRequestObservable() .subscrive(getObserver()) } private Observer<Response> getObserver(){ return new Observer<>(){ ... @Override public void onError(Throwable t){ if(someCondition){ makeRequest(); } } } }
就是每次错误请求的时候自己做判断,满足相关条件则重新请求。这种方案很原始,且若需求稍加变动,则需要手动的更改条件,容易出错。比如现在要做成请求失败后重试3次,每次间隔5秒,这种方案实现起来就很吃力。
-
retryWhen:
retryWhen
每次订阅中执行一次,当接收到.onError()
事件后触发重订阅,所以很适合这个需求场景,代码如下://重试3次,分别延迟5s,10s,15s getRequestObservable().retryWhen(attempt -> { return attempt.zipWith(Observable.range(1, 3), (n, i) -> i) .flatMap(i -> Observable.timer(5 * i, TimeUnit.SECONDS)); }).subscribe(viewModel -> { });
当发生错误时,会走到
retryWhen
方法中来,attempt
就是错误重试的事件流,然后通过zipWith
方法发送三个流,分别延迟5,10,15秒,这样就很巧妙的实现了重试和延迟的逻辑。还有个
.repeatWhen()
,与.retryWhen()
非常相似,只不过不再响应onError
作为重试条件,而是onCompleted
。 -
retryWhen
与PublishRelay
上述的方法可以放在网络请求在后台的时候进行,但是如果你想在前台的时候将重试事件与某个UI事件绑定起来(比如加载失败的时候,用户点击某个按钮重试),就可以尝试以下方案:
PublishRelay<Long> retryRequest = PublishRelay.create(); getRequestObservable() .retryWhen(attempt -> retryRequest) .subscribe(vm -> { //handle response }, t -> { retryView.setVisibility(View.VISIBLE) }); @OnClick(R.id.retry_view) public void onRetryClicked(){ retryRequest.call(System.currenTimeMills) }
PublishRelay
是RxRelay中的一个库,Relay是中继、接力的意思,PublishRelay
的作用在于,一旦观察者订阅,则将所有随后观察到的项目发送给订阅者。在这段代码中,如果请求失败,则
retryWhen
中其实已经发起了这个事件流,但是无人订阅,直到用户点击了“重试”按钮,点击事件中将事件流回传给retryRequest,从而实现了请求重试。
场景二:错误处理
-
Response<T>
interface Api{ @GET("data") Observable<Response<EventResponse>> getData(); } api.getData() .subcribeOn(Schedules.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(eventResponse -> { int responseCode = eventResponse.code(); switch(responseCode){ HTTP_301: HTTP_403: } },error -> { //only handle IO errors. })
示例中直接取到的是一个完整的响应对象而不是我们的JSON对象,所以可以拿到状态码,在这种情况下我们就得通过检查状态码来做相应处理。但是实际上从400-500并不能算是一次成功的请求,按照这种写法我们还是将其归入到了成功的事件流中,所以真实项目中我们一般这样写:
interface Api{ @GET("data") Observable<EventResponse> getData(); } api.getData() .subcribeOn(Schedules.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(eventResponse -> { // handle event },error -> { if(error instanceof HttpException){ Response response = ((HttpException)error).response; switch(response.code()){ ... } } })
将错误的响应请求包装到一个throwable中,然后在错误流中处理它,这样可以有效的处理错误响应代码。不过我们也可以通过诸如filter和share运算符来处理这类问题。
interface Api{ @GET("data") Observable<Response<EventResponse>> getData(); } Observable<Response<EventResponse>> eventResponse = api.getData() .subcribeOn(Schedules.io()) .observeOn(AndroidSchedulers.mainThread()) .share(); eventResponse.filter(Response::isSuccessful) .subscribe(this::handleSuccessfulResponse); eventResponse.filter(res -> res.code() == HTTP_403) .subscribe(this::handle403Response); eventResponse.filter(res -> res.code() == HTTP_404) .subscribe(this::handle404Response);
通过share方法创建了多个传播点,然后通过拆分该流进行不同的订阅处理,这样可以将事件处理分解的更加彻底,也提供了很大的灵活性。
场景三:Loading框
-
传统写法
api.getData() .subcribeOn(Schedules.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(()-> loadingIndicator.show()) .doOnUnSubscribe(() -> loadingIndicator.hide()) .subcribe(...)
这样写弊端有很多,比如直接将用户界面绑定到了数据流中,视图和UI未分离,容易引起内存泄漏等。
-
状态枚举
enum RequestState{ IDEL,LOADING,COMPLETE,ERROR } BehaviorRelay<RequestState> state = BehavaiorRelay.create(RequestState.IDEL); Observable.just(trigger) .doOnNext(()-> state.call(RequestState.LOADING)) .subcribeOn(Schedules.io()) .flatMap(trigger -> api.getData()) .observeOn(AndroidSchedulers.mainThread()) .doOnError(t -> state.call(RequestState.ERROR)) .doOnComplete(() -> state.call(RequestState.COMPLETE)) .subscribe(...)
这里用到了
BehaviorRelay
,换成Subject
也可以。这样一来这些视图的变化就放在了一个单独的流中,而getData获取的数据流中的业务逻辑可以和视图逻辑分离开来。而在视图的流中,我们只用管加载状态的显示,不用处理任何业务数据,也不关心请求到的实际数据或者错误。
state.subscribe(state -> { switch(state){ IDEL: break; LOADING: loadingIndicator.show(); errorView.hide(); break; COMPLETE: loadingIndicator.hide(); break; ERROR: loadingIndicator.hide(); errorView.show(); break; } })
作者:慕尼黑凌晨四点
原文链接:https://www.jianshu.com/p/75da057b312a