RxJava - 等到重试完成其他活动/碎片中的其他可观察对象

时间:2017-02-05 19:36:55

标签: android android-fragments rx-java reactive-programming

用例:我正在开发一款Android应用,其中包含一个包含4个标签的viewpager,所有标签都是片段。对于每个标签/片段,我必须每隔5分钟连接一个带有Oauth和令牌到期的REST Api。

当前解决方案:使用RxJava并重试当运营商我可以在收到401 HTTP错误时重新进行身份验证。对于订阅和使用的每个Observable流,使用:

/*!
Template: your-theme
*/

因此,当令牌过期时,流消耗它然后执行真正的api调用。

问题:这仅适用于在一个订阅中消费的一个观察者,但我需要允许用户在标签之间切换而不会阻止他/她考虑到401错误可能随时出现任何Api Call中的任何片段。

问题:有没有办法让observables等待其他observable完成 onNext(),它们不在同一个流/订阅者中?实际上在不同的碎片?所以api调用场景将是这样的:

retryWhen(refreshTokenAuthenticator)

几乎同时......

Api Call Fragment A --> request
Api Call Fragment A <-- response 200 Code

Api Call Fragment B --> request
Api Call Fragment B <-- response 401 Code (retryWhen in action)
Api Call Fragment B --> request (refreshToken)
Api Call Fragment B <-- response 200 (with new access token saved in the app)
Api Call Fragment C --> request
Api Call Fragment C <-- response 401 Code (retryWhen in action)
Observable in Fragment C Waits till Observable in Fragment B finish (onNext())

这就是我已经拥有的,每个API调用看起来几乎相同:

Api Call Fragment C --> request
Api Call Fragment C <-- response 200

我的RefreshTokenAuthenticator:

public void getDashboardDetail() {

    Subscription subscription = repository.getDashboard()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .retryWhen(tokenAuthenticator)
            .subscribe(new RestHttpObserver<UserDataDto>() {
                @Override
                public void onUnknownError(Throwable e) {
                    getMvpView().onError(e);
                }

                @Override
                public void onHostUnreachable() {
                    getMvpView().onHostUnreachable();
                }

                @Override
                public void onHttpErrorCode(int errorCode, ErrorDto errorDto) {
                    getMvpView().onHttpErrorCode(errorCode, errorDto);
                }

                @Override
                public void onCompleted() {
                    //Do nothing...
                }

                @Override
                public void onNext(UserDataDto response) {
                    getMvpView().onReceiveUserData(response);
                }
            });

    this.compositeSubscription.add(subscription);

}

}

2 个答案:

答案 0 :(得分:2)

1)使auth令牌缓存的上一次成功结果+提供使此缓存结果无效的方法:

class Auth {
    private Observable<AuthToken> validToken;

    synchronized void invalidateAuthToken() {
        validToken = null;
    }

    synchronized Observable<AuthToken> getAuthToken() {
        if (validToken == null) {
            validToken = repository
                .refreshToken(...) // start async request
                .doOnError(e -> invalidateAuthToken())
                .replay(1); // cache result
        }
        return validToken; // share among all subscribers
    }
}

2)要访问Web服务,请使用以下模式:

Observable<Data1> dataSource1 = 
    Observable.defer(auth.getAuthToken()) // always start from token
        .flatMap(token ->
            repository.fetchData1(token, ...)) // use token to call web service
        .doOnError(e -> auth.invalidateAuthToken())
        .retry(N); // retry N times

答案 1 :(得分:0)

最后,如果应用程序当前正在重新进行身份验证,那么只需添加一个全局(在我的Application类中)布尔值即可。它实际上允许两个401 HTTP错误,但第二个继续在onNext()并重新执行初始observable。我想做一些更具反应性的事情,但至少这解决了我的主要问题。

public class RefreshTokenAuthenticator implements Func1<Observable<? extends Throwable>, Observable<?>> {

private static final int RETRY_COUNT = 1;

private static final int HTTP_ERROR_CODE = 401;

@Inject
private UserRepository repository;

@Inject
private SessionManager sessionManager;

@Inject
private MyApplication application;


@Inject
private RefreshTokenAuthenticator() {
}

@Override
public Observable<?> call(Observable<? extends Throwable> observable) {
    return observable
            .flatMap(new Func1<Throwable, Observable<?>>() {
                int retryCount = 0;

                @Override
                public Observable<?> call(final Throwable throwable) {

                    retryCount++;
                    if (retryCount <= RETRY_COUNT && throwable instanceof HttpException) {
                        int errorCode = ((HttpException) throwable).code();

                        if (errorCode == HTTP_ERROR_CODE) {

                            Log.i("RefreshTokenAuth", "APPLICATION IS AUTHENTICATING = " + application.isAuthenticating);
                            if (!application.isAuthenticating) {
                                application.isAuthenticating = true;

                                String refreshToken = sessionManager.getAuthToken().getRefreshToken();

                                return repository
                                        .refreshToken(refreshToken)
                                        .observeOn(AndroidSchedulers.mainThread())
                                        .subscribeOn(Schedulers.io())
                                        .doOnCompleted(() -> application.isAuthenticating = false)
                                        .doOnNext(tokenDto -> sessionManager.saveAuthToken(tokenDto))
                                        .doOnError(throwable1 -> {
                                            Log.e("RefreshTokenAuth", "DoOnError", throwable1);
                                            application.logout();
                                        });
                            } else {
                                return Observable.just(1).doOnNext(o -> Log.i("RefreshTokenAuth", "Let's try another shot!"));
                            }
                        }
                    }
                    // No more retries. Pass the original Retrofit error through.
                    return Observable.error(throwable);
                }
            });
}

}