用RxJava2 显示/隐藏loading的优雅方式

本文主要探究用Rxjava2 + Retrofit进行网络请求时, 应该在哪里调用 showLoading(), 在哪里调用 hideLoading().

别告诉我, 你是这样调用的....

//网络请求获取用户信息
showLoading();
API.getUserInfo()
       ....
       .subscribe((integer -> {
            hideLoading();
            //dosomething
              ...
       }, e -> {  
            hideLoading();
            //handle error
            ...
       });

当然也无妨, 只不过丑了点... 这里, 我们用doXXX 操作符优雅的解决这个问题.

首先探究下各种 doXXX 操作符的执行时机: (类似于声明周期)

1. 正常执行

测试代码

            Observable.create(emiter -> {
                emiter.onNext(1);
                emiter.onComplete();
            })
                    .doOnSubscribe(d -> LOG.d(TAG, "doOnSubscribe"))
                    .doOnNext(integer -> LOG.d(TAG, "doOnNext"))
                    .doAfterNext(integer -> LOG.d(TAG, "doAfterNext"))
                    .doOnComplete(() -> LOG.d(TAG, "doOnComplete"))
                    .doOnError(throwable -> LOG.d(TAG, "doOnError"))
                    .doOnTerminate(() -> LOG.d(TAG, "doOnTerminate"))
                    .doAfterTerminate(() -> LOG.d(TAG, "doAfterTerminate"))
                    .doFinally(() -> LOG.d(TAG, "doFinally"))
                    .doOnDispose(() -> LOG.d(TAG, "doOnDispose"))
                    .subscribe(integer -> {
                        LOG.d(TAG, "onNext");
                    }, e -> {
                        LOG.d(TAG, "onError");
                    }, () -> {
                        LOG.d(TAG, "onComplete");
                    });

结果

        // doOnSubscribe
        // doOnNext
        // onNext
        // doAfterNext
        // doOnComplete
        // doOnTerminate
        // onComplete
        // doFinally
        // doAfterTerminate

2. 上游抛异常

测试代码

            Observable.create(emiter -> {
                emiter.onError(new NullPointerException());
            })
                    .doOnSubscribe(d -> LOG.d(TAG, "doOnSubscribe"))
                    .doOnNext(integer -> LOG.d(TAG, "doOnNext"))
                    .doAfterNext(integer -> LOG.d(TAG, "doAfterNext"))
                    .doOnComplete(() -> LOG.d(TAG, "doOnComplete"))
                    .doOnError(throwable -> LOG.d(TAG, "doOnError"))
                    .doOnTerminate(() -> LOG.d(TAG, "doOnTerminate"))
                    .doAfterTerminate(() -> LOG.d(TAG, "doAfterTerminate"))
                    .doFinally(() -> LOG.d(TAG, "doFinally"))
                    .doOnDispose(() -> LOG.d(TAG, "doOnDispose"))
                    .subscribe((integer -> {
                        LOG.d(TAG, "onNext");
                    }, e -> {
                        LOG.d(TAG, "onError");
                    }, () -> {
                        LOG.d(TAG, "onComplete");
                    }));

结果

        // doOnSubscribe
        // doOnError
        // doOnTerminate
        // onError
        // doFinally
        // doAfterTerminate

根据以上执行结果可以看出, 不管是正常执行还是上游抛出异常,
开始都会调用 doOnSubscribe,
结束都会调用 doOnTerminate, doFinally, doAfterTerminate .

那么是不是我们可以从以上三个方法中随便选一个作为结束的节点, 调用hideLoading()就可以呢?
非也! 下面来看一个很多人容易忽略的问题, 在下游的 onNext() 方法中抛出异常,

3. 下游 onNext 抛异常

测试代码

Observable.create(emiter -> {
    emiter.onNext(1);
    emiter.onComplete();
})
    .doOnSubscribe(d -> LOG.d(TAG, "doOnSubscribe"))
    .doOnNext(integer -> LOG.d(TAG, "doOnNext"))
    .doAfterNext(integer -> LOG.d(TAG, "doAfterNext"))
    .doOnComplete(() -> LOG.d(TAG, "doOnComplete"))
    .doOnError(throwable -> LOG.d(TAG, "doOnError"))
    .doOnTerminate(() -> LOG.d(TAG, "doOnTerminate"))
    .doAfterTerminate(() -> LOG.d(TAG, "doAfterTerminate"))
    .doFinally(() -> LOG.d(TAG, "doFinally"))
    .doOnDispose(() -> LOG.d(TAG, "doOnDispose"))
    .subscribe(integer -> {
        LOG.d(TAG, "onNext");
        throw new NullPointerException();  //此处抛出异常
    }, e -> {
        LOG.d(TAG, "onError");
    }, () -> {
        LOG.d(TAG, "onComplete");
    });

结果

        // doOnSubscribe
        // doOnNext
        // onNext
        // doOnDispose
        // doFinally
        // onError
        // doAfterNext

onNext()中抛出异常就是你的业务逻辑中报错了, 此时会调用onError(), 但不会调用doOnError, doOnTerminatedoAfterTerminate.
所以如果你选择了doOnTerminate 或者 doAfterTerminate 作为了你的结束节点, 那么就可能会出现loading显示后无法隐藏的问题..

综上, 我们找到了整个事件的首尾节点:
开始必然执行 doOnSubscribe
结束必然执行 doFinally

优雅的方式:

//网络请求获取用户信息
API.getUserInfo()
       .doOnSubscribe(d -> showLoading())
       .doFinally(() -> hideLoading())
       .subscribe((integer -> {
             //dosomething
       }, e -> {  
            //handle error
       });

推荐阅读更多精彩内容