RxJava配合Retrofit的一些场景

场景一:retry request

  • 原始方案:

    private void makeRequest(){
        getRequestObservable()
            .subscrive(getObserver())
    }
    
    private Observer 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

  • retryWhenPublishRelay

    上述的方法可以放在网络请求在后台的时候进行,但是如果你想在前台的时候将重试事件与某个UI事件绑定起来(比如加载失败的时候,用户点击某个按钮重试),就可以尝试以下方案:

    PublishRelay 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

    interface Api{
        @GET("data")
        Observable> 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 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> getData();
    }
    
    Observable> 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 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;
        }
    })
    

你可能感兴趣的:(RxJava配合Retrofit的一些场景)