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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24562716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 08:25:36  来源:igfitidea点击:

How to retry HTTP requests with OkHttp/Retrofit?

androidretrofitokhttp

提问by Jaguar

I am using Retrofit/OkHttp (1.6) in my Android project.

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

I don't find any request retry mechanism built-in to either of them. On searching more, I read OkHttp seems to have silent-retries. I don't see that happening on any of my connections (HTTP or HTTPS). How to configure retries with okclient ?

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

For now, I am catching exceptions and retrying maintaining a counter variable.

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

回答by Sinan Kozak

For Retrofit 2.x;

对于改造 2.x;

You can use Call.clone()method to clone request and execute it.

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

For Retrofit 1.x;

对于改造 1.x;

You can use Interceptors. Create a custom interceptor

您可以使用拦截器。创建自定义拦截器

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

And use it while creating RestAdapter.

并在创建 RestAdapter 时使用它。

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

回答by Jonas Lüthke

I don't know if this is an option for you but you could use RxJavatogether with Retrofit.

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

Retrofit is able to return Observables upon rest calls. On Oberservables you can just call retry(count)to resubscribe to the Observable when it emits an error.

Retrofit 能够在 rest 调用时返回 Observable。在retry(count)Oberservables 上,当它发出错误时,您可以调用重新订阅 Observable。

You would have to define the call in your interface like this:

您必须像这样在接口中定义调用:

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

Then you can subscribe to this Observable like this:

然后你可以像这样订阅这个 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

I had the same problem like you and this was actually my solution. RxJava is a really nice library to use in combination with Retrofit. You can even do many cool things in addition to retrying (like e.g. composing and chaining calls).

我和你有同样的问题,这实际上是我的解决方案。RxJava 是一个非常好的库,可以与 Retrofit 结合使用。除了重试之外,您甚至可以做很多很酷的事情(例如组合和链接调用)。

回答by Santiago SR

The problem with response.isSuccessful() is when you have an exception like SocketTimeoutException.

response.isSuccessful() 的问题是当您有像 SocketTimeoutException 这样的异常时。

I modified the original code to fix it.

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

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

Hope it helps. Regards.

希望能帮助到你。问候。

回答by Shreyas

I am of the opinion that you shouldn't mix API handling (done by retrofit/okhttp) with retries. Retrying mechanisms are more orthogonal, and can be used in many other contexts as well. So I use Retrofit/OkHTTP for all the API calls and request/response handling, and introduce another layer above, for retrying the API call.

我认为您不应该将 API 处理(通过改造/okhttp 完成)与重试混合使用。重试机制更加正交,也可以在许多其他上下文中使用。因此,我将 Retrofit/OkHTTP 用于所有 API 调用和请求/响应处理,并在上面引入另一层,用于重试 API 调用。

In my limited Java experience so far, I have found jhlaterman's Failsafe library (github: jhalterman/failsafe) to be a very versatile library for handling many 'retry' situations cleanly. As an example, here's how I would use it with a retrofit instantiated mySimpleService, for authentication -

到目前为止,在我有限的 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;
});

The code above catches socket exceptions, connection errors, assertion failures, and retries on them maximum of 3 times, with exponential backoff. It also allows you to customise on-retry behaviour, and allows you to specify a fallback as well. It's quite configurable, and can adapt to most of the retry situations.

上面的代码捕获套接字异常、连接错误、断言失败,并在指数退避的情况下最多重试 3 次。它还允许您自定义重试行为,并允许您指定回退。它的可配置性很强,可以适应大多数重试情况。

Feel free to check the documentation of the library as it offers many other goodies apart from just retries.

请随意查看库的文档,因为除了重试之外,它还提供了许多其他好处。

回答by Irshu

Courtesy to the top answer,This is what worked for me. If there is a connectivity issues, its better to wait for a few seconds before retry.

感谢最高答案,这对我有用。如果存在连接问题,最好等待几秒钟再重试。

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

}

}

回答by Yamashiro Rion

A solution that worked for me on OkHttp 3.9.1 (considering other answers for this question):

在 OkHttp 3.9.1 上对我有用的解决方案(考虑此问题的其他答案):

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

回答by sahar

For those prefer an interceptor to deal with the issue of retrying - Building upon Sinan's answer, here is my proposed interceptor, which includes both retry count and back-off delay, and only retries attempts when network is available, and when request wasn't cancelled. (only deals with IOExceptions (SocketTimeout, UnknownHost, etc.))

对于那些更喜欢拦截器来处理重试问题的人 - 基于思南的回答,这是我建议的拦截器,其中包括重试计数和回退延迟,并且仅在网络可用且请求不存在时重试取消。(仅处理 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;
        }
    });

回答by osexp2003

I found the way(OKHttpClient intercepter) provided by Sinan Kozak does not work when http connection failed, there is nothing yet concerned with HTTP response.

我发现Sinan Kozak提供的方式(OKHttpClient拦截器)在http连接失败时不起作用,还没有与HTTP响应有关。

So i use another way to hook the Observable object, call .retryWhen on it. Also, i have added retryCount limit.

所以我使用另一种方法来挂钩 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.Hymanson.HymansonConverterFactory;
import rx.Observable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

Then

然后

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

Then add or replace

然后添加或替换

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())

with

.addCallAdapterFactory(newCallAdaptorFactory)

For example:

例如:

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

Note: For simplicity, i just treat HTTP code > 404 code as retry, please modify it for yourself.

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

Besides, if http response is 200, then above rx.retryWhenwill not get called, if you insist check such a response, you can add rx.subscribeOn(...throw error...before .retryWhen.

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

回答by Stoycho Andreev

I have play a lot with this problem trying to find how is the best way to retry Retrofit requests. I am using Retrofit 2 so my solution is for Retrofit 2. For Retrofit 1 you have to use Interceptor like the accepted answer here. The answer of @joluet is correct but he did not mention that retry method need to be called before .subscribe(onComplete, onError) method. This is very important otherwise the request wouldn't be retried again like @pocmo mentioned in @joluet answer. Here is my example:

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

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 is my RetrofitServiceProvider object.

apiService 是我的 RetrofitServiceProvider 对象。

BTW : I am using Java 8 so a lot of lambda expressions are inside the code.

顺便说一句:我使用的是 Java 8,所以代码中有很多 lambda 表达式。

回答by Nokuap

Just want to share my version. It uses rxJava retryWhen method. My version retries connection every N=15 sec and almost immediately emit retry when internet connection recover.

只是想分享我的版本。它使用 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());
}}

And you should use it before .subscribe like this:

你应该像这样在 .subscribe 之前使用它:

.retryWhen(new RetryWithDelayOrInternet())

You should manually change isInternetUp field

您应该手动更改 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();
}}