How to retry HTTP requests with OkHttp/Retrofit?
我在Android项目中使用Retrofit / OkHttp(1.6)。
我找不到任何内置的请求重试机制。 在搜索更多内容时,我读到OkHttp似乎有静默重试。 我没有在任何连接(HTTP或HTTPS)上看到这种情况。 如何使用okclient配置重试?
目前,我正在捕获异常并重试维护一个计数器变量。
对于翻新2.x;
您可以使用Call.clone()方法克隆请求并执行它。
对于翻新1.x;
您可以使用拦截器。创建一个自定义拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | OkHttpClient client = new OkHttpClient(); client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.interceptors().add(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); // try the request Response response = chain.proceed(request); int tryCount = 0; while (!response.isSuccessful() && tryCount < 3) { Log.d("intercept","Request is not successful -" + tryCount); tryCount++; // retry the request response = chain.proceed(request); } // otherwise just pass the original response on return response; } }); |
并在创建RestAdapter时使用它。
1 2 3 4 5 6 | new RestAdapter.Builder() .setEndpoint(API_URL) .setRequestInterceptor(requestInterceptor) .setClient(new OkClient(client)) .build() .create(Adapter.class); |
我不知道这是否适合您,但是您可以将RxJava与Retrofit一起使用。
翻新能够在休息呼叫时返回Observable。在Oberservables上,您可以在出现错误时调用
您必须像这样在界面中定义调用:
1 2 | @GET("/data.json") Observable<DataResponse> fetchSomeData(); |
然后,您可以像下面这样订阅此Observable:
1 2 3 4 5 6 7 | restApi.fetchSomeData() .retry(5) // Retry the call 5 times if it errors .subscribeOn(Schedulers.io()) // execute the call asynchronously .observeOn(AndroidSchedulers.mainThread()) // handle the results in the ui thread .subscribe(onComplete, onError); // onComplete and onError are of type Action1<DataResponse>, Action1<Throwable> // Here you can define what to do with the results |
我和您一样有同样的问题,这实际上是我的解决方案。 RxJava是一个非常不错的库,可以与Retrofit结合使用。除了重试外,您甚至可以做很多很酷的事情(例如,编写和链接呼叫)。
response.isSuccessful()的问题是当您遇到类似SocketTimeoutException的异常时。
我修改了原始代码以对其进行修复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | OkHttpClient client = new OkHttpClient(); client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.interceptors().add(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; boolean responseOK = false; int tryCount = 0; while (!responseOK && tryCount < 3) { try { response = chain.proceed(request); responseOK = response.isSuccessful(); }catch (Exception e){ Log.d("intercept","Request is not successful -" + tryCount); }finally{ tryCount++; } } // otherwise just pass the original response on return response; } }); |
希望能帮助到你。
问候。
我认为您不应将API处理(通过Retrofit / okhttp完成)与重试混合使用。重试机制更加正交,也可以在许多其他上下文中使用。因此,我将Retrofit / OkHTTP用于所有API调用和请求/响应处理,并在上面引入另一层以重试API调用。
到目前为止,以我有限的Java经验,我发现jhlaterman的Failsafe库(github:jhalterman / failsafe)是一种用途广泛的库,可以干净地处理许多"重试"情况。举例来说,这是我如何将其与经过实例化的mySimpleService进行改造以进行身份??验证的方法-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | AuthenticationResponse authResp = Failsafe.with( new RetryPolicy().retryOn(Arrays.asList(IOException.class, AssertionError.class)) .withBackoff(30, 500, TimeUnit.MILLISECONDS) .withMaxRetries(3)) .onRetry((error) -> logger.warn("Retrying after error:" + error.getMessage())) .get(() -> { AuthenticationResponse r = mySimpleAPIService.authenticate( new AuthenticationRequest(username,password)) .execute() .body(); assert r != null; return r; }); |
上面的代码捕获套接字异常,连接错误,断言失败,并对其进行最多3次重试,并以指数补偿。它还允许您自定义重试行为,还可以指定回退。它是可配置的,并且可以适应大多数重试情况。
可以随意查看该库的文档,因为它提供了许多其他功能,而不仅仅是重试。
礼貌的回答,这对我有用。如果存在连接问题,最好等待几秒钟再重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class ErrorInterceptor implements Interceptor { ICacheManager cacheManager; Response response = null; int tryCount = 0; int maxLimit = 3; int waitThreshold = 5000; @Inject public ErrorInterceptor() { } @Override public Response intercept(Chain chain){ // String language = cacheManager.readPreference(PreferenceKeys.LANGUAGE_CODE); Request request = chain.request(); response = sendReqeust(chain,request); while (response ==null && tryCount < maxLimit) { Log.d("intercept","Request failed -" + tryCount); tryCount++; try { Thread.sleep(waitThreshold); // force wait the network thread for 5 seconds } catch (InterruptedException e) { e.printStackTrace(); } response = sendReqeust(chain,request); } return response; } private Response sendReqeust(Chain chain, Request request){ try { response = chain.proceed(request); if(!response.isSuccessful()) return null; else return response; } catch (IOException e) { return null; } } |
}
在OkHttp 3.9.1上对我有用的解决方案(考虑此问题的其他答案):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | @NonNull @Override public Response intercept(@NonNull Chain chain) throws IOException { Request request = chain.request(); int retriesCount = 0; Response response = null; do { try { response = chain.proceed(request); // Retry if no internet connection. } catch (ConnectException e) { Log.e(TAG,"intercept:", e); retriesCount++; try { Thread.sleep(RETRY_TIME); } catch (InterruptedException e1) { Log.e(TAG,"intercept:", e1); } } } while (response == null && retriesCount < MAX_RETRIES); // If there was no internet connection, then response will be null. // Need to initialize response anyway to avoid NullPointerException. if (response == null) { response = chain.proceed(newRequest); } return response; } |
对于那些更喜欢拦截器来处理重试问题的人-
基于Sinan的答案,这是我建议的拦截器,其中包括重试计数和退避延迟,并且仅在网络可用且请求未取消时重试尝试。
(仅处理IOExceptions(SocketTimeout,UnknownHost等)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | builder.addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); // try the request Response response = null; int tryCount = 1; while (tryCount <= MAX_TRY_COUNT) { try { response = chain.proceed(request); break; } catch (Exception e) { if (!NetworkUtils.isNetworkAvailable()) { // if no internet, dont bother retrying request throw e; } if ("Canceled".equalsIgnoreCase(e.getMessage())) { // Request canceled, do not retry throw e; } if (tryCount >= MAX_TRY_COUNT) { // max retry count reached, giving up throw e; } try { // sleep delay * try count (e.g. 1st retry after 3000ms, 2nd after 6000ms, etc.) Thread.sleep(RETRY_BACKOFF_DELAY * tryCount); } catch (InterruptedException e1) { throw new RuntimeException(e1); } tryCount++; } } // otherwise just pass the original response on return response; } }); |
我发现当HTTP连接失败时,Sinan Kozak提供的方式(OKHttpClient拦截器)不起作用,目前还没有与HTTP响应有关的问题。
所以我用另一种方式钩住Observable对象,在其上调用.retryWhen。
另外,我添加了retryCount限制。
1 2 3 4 5 6 7 8 9 10 | import retrofit2.Call; import retrofit2.CallAdapter; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.HttpException; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; import rx.Observable; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; |
然后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | RxJavaCallAdapterFactory originCallAdaptorFactory = RxJavaCallAdapterFactory.create(); CallAdapter.Factory newCallAdaptorFactory = new CallAdapter.Factory() { @Override public CallAdapter< ? > get(Type returnType, Annotation[] annotations, Retrofit retrofit) { CallAdapter< ? > ca = originCallAdaptorFactory.get(returnType, annotations, retrofit); return new CallAdapter<Observable< ? >>() { @Override public Type responseType() { return ca.responseType(); } int restRetryCount = 3; @Override public <R> Observable< ? > adapt(Call<R> call) { Observable< ? > rx = (Observable< ? >) ca.adapt(call); return rx.retryWhen(errors -> errors.flatMap(error -> { boolean needRetry = false; if (restRetryCount >= 1) { if (error instanceof IOException) { needRetry = true; } else if (error instanceof HttpException) { if (((HttpException) error).code() != 200) { needRetry = true; } } } if (needRetry) { restRetryCount--; return Observable.just(null); } else { return Observable.error(error); } })); } }; } }; |
然后
添加或替换
1 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) |
与
1 | .addCallAdapterFactory(newCallAdaptorFactory) |
例如:
1 2 3 4 5 6 | return new Retrofit .Builder() .baseUrl(baseUrl) .client(okClient) .addCallAdapterFactory(newCallAdaptorFactory) .addConverterFactory(JacksonConverterFactory.create(objectMapper)); |
注意:为简单起见,我只将HTTP代码> 404代码视为重试,请自行修改。
此外,如果http响应为200,则不会调用
工作产品解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public int callAPI() { return 1; //some method to be retried } public int retrylogic() throws InterruptedException, IOException{ int retry = 0; int status = -1; boolean delay = false; do { if (delay) { Thread.sleep(2000); } try { status = callAPI(); } catch (Exception e) { System.out.println("Error occured"); status = -1; } finally { switch (status) { case 200: System.out.println(" **OK**"); return status; default: System.out.println(" **unknown response code**."); break; } retry++; System.out.println("Failed retry" + retry +"/" + 3); delay = true; } }while (retry < 3); System.out.println("Aborting download of dataset."); return status; } |
只想分享我的版本。它使用rxJava retryWhen方法。我的版本每N = 15秒重试一次连接,当Internet连接恢复时几乎立即发出重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class RetryWithDelayOrInternet implements Function<Flowable<? extends Throwable>, Flowable< ? >> { public static boolean isInternetUp; private int retryCount; @Override public Flowable< ? > apply(final Flowable<? extends Throwable> attempts) { return Flowable.fromPublisher(s -> { while (true) { retryCount++; try { Thread.sleep(1000); } catch (InterruptedException e) { attempts.subscribe(s); break; } if (isInternetUp || retryCount == 15) { retryCount = 0; s.onNext(new Object()); } } }) .subscribeOn(Schedulers.single()); }} |
您应该在.subscribe之前使用它,如下所示:
1 | .retryWhen(new RetryWithDelayOrInternet()) |
您应该手动更改isInternetUp字段
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class InternetConnectionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { boolean networkAvailable = isNetworkAvailable(context); RetryWithDelayOrInternet.isInternetUp = networkAvailable; } public static boolean isNetworkAvailable(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); return activeNetworkInfo != null && activeNetworkInfo.isConnected(); }} |
我在解决这个问题上扮演了很多角色,试图找到重试翻新请求的最佳方法。我正在使用Retrofit 2,所以我的解决方案是Retrofit2。对于Retrofit 1,您必须像此处接受的答案一样使用Interceptor。 @joluet的答案是正确的,但他没有提到需要在.subscribe(onComplete,onError)方法之前调用重试方法。这非常重要,否则将不会像@joluet答案中提到的@pocmo一样重试该请求。这是我的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | final Observable<List<NewsDatum>> newsDetailsObservable = apiService.getCandidateNewsItem(newsId).map((newsDetailsParseObject) -> { return newsDetailsParseObject; }); newsDetailsObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry((integer, throwable) -> { //MAX_NUMBER_TRY is your maximum try number if(integer <= MAX_NUMBER_TRY){ return true;//this will retry the observable (request) } return false;//this will not retry and it will go inside onError method }) .subscribe(new Subscriber<List<NewsDatum>>() { @Override public void onCompleted() { // do nothing } @Override public void onError(Throwable e) { //do something with the error } @Override public void onNext(List<NewsDatum> apiNewsDatum) { //do something with the parsed data } }); |
apiService是我的RetrofitServiceProvider对象。
顺便说一句:我正在使用Java 8,因此代码中包含许多lambda表达式。
如文档中所述,更好的方法是在身份验证器中使用烘焙的方法,例如:
私有的最终OkHttpClient客户= new OkHttpClient();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public void run() throws Exception { client.setAuthenticator(new Authenticator() { @Override public Request authenticate(Proxy proxy, Response response) { System.out.println("Authenticating for response:" + response); System.out.println("Challenges:" + response.challenges()); String credential = Credentials.basic("jesse","password1"); return response.request().newBuilder() .header("Authorization", credential) .build(); } @Override public Request authenticateProxy(Proxy proxy, Response response) { return null; // Null indicates no attempt to authenticate. } }); Request request = new Request.Builder() .url("http://publicobject.com/secrets/hellosecret.txt") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code" + response); System.out.println(response.body().string()); } |
看来它会出现在API Spec的改造2.0中:
https://github.com/square/retrofit/issues/297。
当前,最好的方法似乎是捕获异常并手动重试。