首页 文章

如何使用OkHttp / Retrofit重试HTTP请求?

提问于
浏览
49

我在我的Android项目中使用Retrofit / OkHttp(1.6) .

我没有找到内置于其中任何一个的任何请求重试机制 . 在搜索更多时,我看到OkHttp似乎有沉默重试 . 我没有看到我的任何连接(HTTP或HTTPS)发生这种情况 . 如何使用okclient配置重试?

现在,我正在捕获异常并重试维护计数器变量 .

11 回答

  • 0

    我发现当http连接失败时,Sinan Kozak提供的方式(OKHttpClient intercepter)不起作用,还没有关注HTTP响应 .

    所以我用另一种方法来挂钩Observable对象,在它上面调用.retryWhen . 另外,我添加了retryCount限制 .

    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;
    

    然后

    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);
                            }
                        }));
                    }
                };
            }
        };
    

    然后添加或替换

    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    

    .addCallAdapterFactory(newCallAdaptorFactory)
    

    例如:

    return new Retrofit
            .Builder()
            .baseUrl(baseUrl)
            .client(okClient)
            .addCallAdapterFactory(newCallAdaptorFactory)
            .addConverterFactory(JacksonConverterFactory.create(objectMapper));
    

    注意:为简单起见,我只是将HTTP代码> 404代码视为重试,请自行修改 .

    此外,如果http响应为200,那么上面的 rx.retryWhen 将不会被调用,如果你坚持检查这样的响应,你可以在.retryWhen之前添加 rx.subscribeOn(...throw error... .

  • 1

    For Retrofit 1.x;

    你可以使用Interceptors . 创建自定义拦截器

    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时使用它 .

    new RestAdapter.Builder()
            .setEndpoint(API_URL)
            .setRequestInterceptor(requestInterceptor)
            .setClient(new OkClient(client))
            .build()
            .create(Adapter.class);
    

    For Retrofit 2.x;

    您可以使用Call.clone()方法克隆请求并执行它 .

  • 1

    我不知道这是否适合您,但您可以将RxJava与Retrofit一起使用 .

    Retrofit能够在休息时调用Observables . 在Oberservables上,您可以在发出错误时调用 retry(count) 重新订阅Observable .

    您必须在界面中定义调用,如下所示:

    @GET("/data.json")
    Observable<DataResponse> fetchSomeData();
    

    然后你可以像这样订阅这个Observable:

    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结合使用 . 除了重试之外,你甚至可以做很多很酷的事情(例如composing and chaining calls) .

  • 63

    response.isSuccessful()的问题是当你有一个像SocketTimeoutException这样的异常时 .

    我修改了原始代码来修复它 .

    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;
        }
    });
    

    希望能帮助到你 . 问候 .

  • 1

    对于那些更喜欢拦截器来处理重试的问题 - 在Sinan的答案的基础上,这里是我提出的拦截器,其中包括重试计数和退避延迟,并且只在网络可用时重试尝试,并且当请求不是时取消 . (仅处理IOExceptions(SocketTimeout,UnknownHost等))

    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;
            }
        });
    
  • 33

    礼貌地回答,这对我有用 . 如果存在连接问题,最好在重试前等待几秒钟 .

    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;
        }
    }
    

    }

  • 1

    我认为你不应该混合API处理(由retrofit / okhttp完成)和重试 . 重试机制更正交,也可以在许多其他环境中使用 . 所以我使用Retrofit / OkHTTP进行所有API调用和请求/响应处理,并在上面引入另一个层,用于重试API调用 .

    在我迄今为止有限的Java经验中,我发现jhlaterman的Failsafe库(github:jhalterman/failsafe)是一个非常通用的库,可以干净地处理许多情况 . 作为一个例子,我将如何使用它与改进的实例化mySimpleService进行身份验证 -

    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次,指数退避 . 它还允许您自定义重试行为,并允许您指定回退 . 它非常易于配置,可以适应大多数重试情况 .

    随意检查库的文档,因为除了重试之外它还提供许多其他好东西 .

  • 0

    它似乎将出现在API Spec:https://github.com/square/retrofit/issues/297的改进2.0中 . 目前,最好的方法似乎是捕获异常并手动重试 .

  • 0

    docs中所述,更好的方法是在验证器中使用烘焙,例如:private final OkHttpClient client = new OkHttpClient();

    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());
      }
    
  • 10

    我已经尝试了很多这个问题,试图找到重试Retrofit请求的最佳方法 . 我正在使用Retrofit 2,所以我的解决方案是Retrofit 2.对于Retrofit 1,你必须使用Interceptor,就像这里接受的答案一样 . @joluet的答案是正确的,但他没有提到需要在.subscribe(onComplete,onError)方法之前调用重试方法 . 这非常重要,否则请求不会再次重试@pocmo在@joluet中提到了答案 . 这是我的例子:

    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表达式 .

  • 0

    只想分享我的版本 . 它使用rxJava retryWhen方法 . 我的版本每N = 15秒重试连接,并在互联网连接恢复时几乎立即发出重试 .

    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如下:

    .retryWhen(new RetryWithDelayOrInternet())
    

    您应该手动更改isInternetUp字段

    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();
    }}
    

相关问题