Retrofit2 + RxJava2,无效令牌,retryWhen()重新订阅时如何更新流

时间:2017-04-19 06:26:21

标签: rx-java retrofit2 rx-java2 reactive resque-retry

我在下面有这个简单的代码,模拟我正在尝试完成的场景

mApiService.api().postSomethingWithAccessToken(request, "some_invalid_access_token")
            .subscribeOn(Schedulers.io())
            .retryWhen(new Function<Observable<Throwable>, ObservableSource<AccessToken>>() {

                @Override
                public ObservableSource<AccessToken> apply(Observable<Throwable> throwableObservable) throws Exception {
                    return mApiService.api().getAccessToken();
                }
            })
            .subscribeOn(Schedulers.io())
            .subscribe(new Observer<Void>() {
                @Override
                public void onSubscribe(Disposable d) {
                }

                @Override
                public void onNext(Void value) {
                }

                @Override
                public void onError(Throwable e) {

                    e.printStackTrace();
                    onError(e);
                }

                @Override
                public void onComplete() {
                }
            });

我只是列举它以明确我的目标:

  1. 使用当前访问令牌执行 POST 调用
  2. 如果收到适当的错误(404,403,401等)
  3. 执行 GET 调用以获得新的访问令牌
  4. 使用新的访问令牌重试整个序列
  5. 基于上面的代码以及我目前对 .retryWhen()的理解,如果原始可观察上发生错误,它将会执行(。 postSomethingWithAccessToken()),并在必要时重试(根据你在重试中的条件),这里发生的是 .retryWhen()首先在外部Observable之前执行,导致不需要的重复请求, 根据我目前的理解(代码),我怎样才能实现上面提到的那些东西?任何帮助将不胜感激。 :(

    编辑:当前解决方法:

    mApiService.api().postSomethingWithAccessToken(request, preferences.getString("access_token", ""))
                .subscribeOn(Schedulers.io())
                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
    
                    @Override
                    public ObservableSource<?> apply(final Observable<Throwable> throwableObservable) throws Exception {
    
                        return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
    
                            @Override
                            public ObservableSource<?> apply(Throwable throwable) throws Exception {
    
                                if (throwable instanceof HttpException) {
    
                                    HttpException httpException = (HttpException) throwable;
    
                                    if (httpException.code() == 401) {
    
                                        return mApiService.api().getAccessToken()
                                                .doOnNext(new Consumer<Authentication>() {
                                                    @Override
                                                    public void accept(Authentication authentication) throws Exception {
                                                        update(authentication);
                                                    }
                                                });
                                    }
                                }
    
                                return Observable.error(throwable);
                            }
                        });
                    }
                })
                .subscribe(new Observer<Void>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.e("subscribe", "TOKEN : " + preferences.getString("access_token", ""));
                    }
    
                    @Override
                    public void onNext(Void value) {
                        Log.e("onNext", "TOKEN : " + preferences.getString("access_token", ""));
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }
    
                    @Override
                    public void onComplete() {
                        Log.e("Complete", "____ COMPLETE");
                    }
                });
    

    通过共享偏好更新令牌的方法

    public void update(Authentication authentication) {
        preferences.edit().putString("access_token", authentication.getAccessToken()).commit();
    }
    

    我注意到(我把一个Log)外部observable的订阅和retryWhen在主线程上执行,但是重试/重新订阅的流正跳过不同的Scheduler的线程,看起来像竞争条件:(

        onSubscrbie_outer_observable: Thread[main,5,main]
        RetryWhen: Thread[main,5,main]
        Throwable_FlatMap: Thread[RxCachedThreadScheduler-1,5,main]
        doOnNext(Token_Refresh): Thread[RxCachedThreadScheduler-1,5,main]
        Throwable_FlatMap: Thread[RxCachedThreadScheduler-2,5,main]
        doOnNext(Token_Refresh): Thread[RxCachedThreadScheduler-2,5,main]
        Throwable_FlatMap: Thread[RxCachedThreadScheduler-1,5,main]
        doOnNext(Token_Refresh): Thread[RxCachedThreadScheduler-1,5,main]
        // and so on...
    

3 个答案:

答案 0 :(得分:3)

这里几乎没有问题:

  • 您需要在重试时将访问令牌传回postSomethingWithAccessToken方法,否则您只需使用相同的旧无效访问令牌重试。
  • 当逻辑不正确时你的重试,你必须回应你得到的错误Observable并将你的重试逻辑放在那里。正如您所说的那样,首先执行此方法,而不是在发生错误时,throwableObservable是对错误的响应,它会将错误视为排放(onNext()),您可以flatMap()每个错误并且响应错误(用于向源流传递错误)完成,或者使用onNext()并使用某个对象发出信号进行重试。
    关于这个主题的blog post ban Dan Lew很棒。

所以你需要:
1)将访问令牌存储在可以使用访问令牌刷新进行更改的地方 2)在逻辑正确响应错误时修复重试

以下是建议代码:

postSomethingWithAccessToken(request, accessToken)
        .subscribeOn(Schedulers.io())
        .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
                   @Override
                   public ObservableSource<?> apply(
                           @NonNull Observable<Throwable> throwableObservable) throws Exception {
                       return throwableObservable.flatMap(
                               new Function<Throwable, ObservableSource<? extends R>>() {
                                   @Override
                                   public ObservableSource<? extends R> apply(
                                           @NonNull Throwable throwable) throws Exception {
                                       if (throwable.code == 401) { //or 404/403, just a pseudo-code, put your real error comparing logic here
                                           return getAccessToken()
                                                           .doOnNext(refreshedToken -> accessToken.updateToken(refreshedToken));
                                                   //or keep accessToken on some field, the point to have mutable
                                                   //var that you can change and postSomethingWithAccessToken can see
                                       }
                                       return Observable.error(throwable);
                                   }
                               });
                       }
                   }
        )
        .subscribeOn(Schedulers.io())
        .subscribe(new Consumer<Result>() {
                       @Override
                       public void accept(@NonNull Result result) throws Exception {
                           //handle result
                       }
                   }
        );

答案 1 :(得分:1)

非常感谢yosriz指出我正确的方向来解决我的磨牙问题,我必须使用defer。所以我最终在GitHub中遇到了这个问题,Why resubscribe the source observable emit same output when I use retryWhen operator?

这与我现在遇到的问题完全相同,因为在这里遇到同样问题的任何人都是我的解决方案。

Observable
    .defer(new Callable<ObservableSource<?>>() {
        @Override
        public ObservableSource<?> call() throws Exception {
            // return an observable source here, the observable that will be the source of the entire stream;
        }
    })
    .subscribeOn( /*target thread to run*/ )
    .retryWhen( {
        // return a throwable observable here that will perform the logic when an error occurred
    })
    .subscribe( /*subscription here*/ )

或者这是我的解决方案的完整非lambda

Observable
    .defer(new Callable<ObservableSource<?>>() {
        @Override
        public ObservableSource<?> call() throws Exception {
            return mApiService.api().postSomethingWithAccessToken(
                request, preferences.getString("access_token", ""));
        }
    })
    .subscribeOn(Schedulers.io())
    .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
        @Override
        public ObservableSource<?> apply(final Observable<Throwable> throwableObservable) throws Exception {
            return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(Throwable throwable) throws Exception {
                    if (throwable instanceof HttpException) {
                        HttpException httpException = (HttpException) throwable;
                        if (httpException.code() == 401) {
                            return mApiService.api().getAccessToken().doOnNext(new Consumer<Authentication>() {
                                    @Override
                                    public void accept(Authentication authentication) throws Exception {
                                        update(authentication);
                                    }
                                });
                        }
                    }
                    return Observable.error(throwable);
                }
            });
        }
    })
    .subscribe(new Observer<Void>() {
        @Override
        public void onSubscribe(Disposable d) {
            Log.e("subscribe", "TOKEN : " + preferences.getString("access_token", ""));
        }

        @Override
        public void onNext(Void value) {
            Log.e("onNext", "TOKEN : " + preferences.getString("access_token", ""));
        }

        @Override
        public void onError(Throwable e) {
            e.printStackTrace();
        }

        @Override
        public void onComplete() {
            Log.e("Complete", "____ COMPLETE");
        }
    });

这里的关键点是“当.retryWhen()运算符重新订阅源可观察时,如何修改/更新现有的源可观察源”

答案 2 :(得分:0)

我试图在这里解决同样的问题,我试图复制上面的解决方案,它确实刷新了令牌,但是当我的令牌更新时没有尝试重试请求。

这是我没有lambda的代码:

public Observable<Estabelecimento> listarEstabelecimentos() {
    return Observable.defer(new Callable<ObservableSource<? extends Estabelecimento>>() {
        @Override
        public ObservableSource<? extends Estabelecimento> call() throws Exception {
            return mGetNetAPI.listarEstabelecimento()
                    .map(mNetworkErrorHandler::processError);
        }
    }).retryWhen(throwableObservable -> throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                                                                        @Override
                                                                        public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                                                                            if (throwable instanceof UnauthorizedException) {
                                                                                return mRequestManager.getTokenObservable(AutoAtendimentoApplication.getContext())
                                                                                        .doOnNext(new Consumer<AuthResponse>() {
                                                                                            @Override
                                                                                            public void accept(@NonNull AuthResponse response) throws Exception {
                                                                                                Log.i("NEXT", "OK");
                                                                                            }
                                                                                        }).doOnError(new Consumer<Throwable>() {
                                                                                            @Override
                                                                                            public void accept(@NonNull Throwable throwable) throws Exception {
                                                                                                Log.i("ONERROR", "NOT OK");
                                                                                            }
                                                                                        });

                                                                            }

                                                                            return Observable.error(throwable);
                                                                        }
                                                                    }
    ));
}

任何想法我可能做错了吗?