Как повторить HTTP-запросы с помощью OkHttp / Retrofit?

Я использую Retrofit / OkHttp (1.6) в своем проекте Android.

Я не нашел механизм повторной попытки запроса, встроенный в любой из них. При поиске больше, я читаю OkHttp, похоже, молчат. Я не вижу, что это происходит на любом из моих соединений (HTTP или HTTPS). Как настроить повторы с помощью okclient?

На данный момент я перехватываю исключения и повторяю, сохраняя переменную счетчика.

Для 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); 

Для Retrofit 2.x;

Вы можете использовать метод Call.clone () для клонирования запроса и его выполнения.

Я не знаю, подходит ли это для вас, но вы можете использовать RxJava вместе с Retrofit.

Модернизация может возвращать Observables при переадресации. В Oberservables вы можете просто вызвать retry(count) для повторной подписки на Observable, когда она испускает ошибку.

Вам нужно будет определить вызов в вашем интерфейсе следующим образом:

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

Тогда вы можете подписаться на этот Наблюдаемый следующим образом:

 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.

Я изменил исходный код, чтобы исправить его.

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

Надеюсь, поможет. С уважением.

Я нашел способ (интерпретатор OKHttpClient), предоставленный Синаном Козаком, не работает, когда сбой HTTP-соединения не был, ничего не связано с ответом 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 не будет вызван, если вы настаиваете на проверке такого ответа, вы можете добавить rx.subscribeOn(...throw error... before .retryWhen.

Учтивость к верхнему ответу. Это то, что сработало для меня. Если есть проблемы с подключением, лучше подождать несколько секунд, прежде чем повторить попытку.

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

}

Кажется, он будет представлен в версии 2.0 с API Spec: https://github.com/square/retrofit/issues/297 . В настоящее время лучшим способом является исключение catch и повторное выполнение вручную.

Как указано в документах , лучше всего использовать испеченные в аутентификаторах, например: 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()); } 

Я много разбираюсь в этой проблеме, пытаясь найти способ наилучшего повторения запросов 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.

BTW: Я использую Java 8, поэтому в коде содержится много лямбда-выражений.

Для тех, кто предпочитает перехватчик, чтобы справиться с проблемой повторной попытки. Основываясь на ответе Синана, вот мой предлагаемый перехватчик, который включает в себя как количество повторных попыток, так и задержку отсрочки, и только повторяет попытки, когда сеть доступна, и когда запрос не был отменен. (Работает только с 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 (eg 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; } });