Comment réessayer les requêtes HTTP avec OkHttp / Retrofit?

J’utilise Retrofit / OkHttp (1.6) dans mon projet Android.

Je ne trouve aucun mécanisme de nouvelle tentative de demande intégré à l’un d’eux. En cherchant plus, j’ai lu que OkHttp semble avoir des tentatives silencieuses. Je ne vois pas cela sur aucune de mes connexions (HTTP ou HTTPS). Comment configurer les tentatives avec okclient?

Pour l’instant, j’attrape des exceptions et réessaie de maintenir une variable de compteur.

Pour Retrofit 1.x;

Vous pouvez utiliser des intercepteurs . Créer un intercepteur personnalisé

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

Et utilisez-le lors de la création de RestAdapter.

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

Pour la rénovation 2.x;

Vous pouvez utiliser la méthode Call.clone () pour cloner la demande et l'exécuter.

Je ne sais pas si c’est une option pour vous, mais vous pouvez utiliser RxJava avec Retrofit.

Retrofit est capable de retourner des observables lors des appels de repos. Sur Oberservables, vous pouvez simplement appeler retry(count) pour vous réabonner à Observable lorsqu’il émet une erreur.

Vous devriez définir l’appel dans votre interface comme ceci:

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

Ensuite, vous pouvez vous abonner à cette Observable comme ceci:

 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, Action1 // Here you can define what to do with the results 

J’ai eu le même problème que vous et c’était ma solution. RxJava est une bibliothèque très agréable à utiliser en combinaison avec Retrofit. Vous pouvez même faire beaucoup de choses intéressantes en plus de réessayer (comme par exemple composer et chaîner des appels ).

Le problème avec response.isSuccessful () se produit lorsque vous avez une exception comme SocketTimeoutException.

J’ai modifié le code d’origine pour le réparer.

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

J'espère que cela aide. Cordialement.

J’ai trouvé le moyen (intercepteur OKHttpClient) fourni par Sinan Kozak ne fonctionne pas lorsque la connexion HTTP a échoué, il n’y a encore rien qui concerne la réponse HTTP.

Donc, j’utilise un autre moyen pour accrocher l’object Observable, appelez .retryWhen dessus. De plus, j’ai ajouté la limite 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; 

alors

  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>() { @Override public Type responseType() { return ca.responseType(); } int restRetryCount = 3; @Override public  Observable adapt(Call 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); } })); } }; } }; 

Puis ajoutez ou remplacez

 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 

avec

 .addCallAdapterFactory(newCallAdaptorFactory) 

Par exemple:

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

Note: Pour plus de simplicité, je traite le code HTTP> 404 comme une tentative, modifiez-le vous-même.

En outre, si la réponse http est 200, alors au-dessus de rx.retryWhen ne sera pas appelée, si vous insistez sur une telle réponse, vous pouvez append rx.subscribeOn(...throw error... avant .retryWhen.

Pour ceux qui préfèrent un intercepteur pour résoudre le problème de réessayer – En s’appuyant sur la réponse de Sinan, voici mon intercepteur proposé, qui inclut à la fois le nombre de tentatives et le délai d’attente, et réessaye uniquement lorsque le réseau est disponible et annulé. (traite uniquement des exceptions IOExceptions (SocketTimeout, UnknownHost, etc.))

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

Courtoisie à la réponse supérieure, c’est ce qui a fonctionné pour moi. S’il y a des problèmes de connectivité, il est préférable d’attendre quelques secondes avant de réessayer.

 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){ // Ssortingng 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; } } 

}

Il semble qu’il sera présent dans la version 2.0 de l’API Spec: https://github.com/square/retrofit/issues/297 . Actuellement, le meilleur moyen semble être l’exception de capture et réessayer manuellement.

Comme indiqué dans la documentation , il pourrait être préférable d’utiliser les authentifiants cuits, par exemple: client final final OkHttpClient = 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()); Ssortingng 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().ssortingng()); } 

J’ai beaucoup joué à ce problème en essayant de trouver le meilleur moyen de réessayer les demandes de modification. J’utilise Retrofit 2, donc ma solution est pour Retrofit 2. Pour Retrofit 1, vous devez utiliser Interceptor comme la réponse acceptée ici. La réponse de @joluet est correcte mais il n’a pas mentionné que la méthode de nouvelle tentative doit être appelée avant la méthode .subscribe (onComplete, onError). Ceci est très important, sinon la requête ne serait pas relancée comme @pocmo mentionné dans @joluet answer. Voici mon exemple:

 final Observable> 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>() { @Override public void onCompleted() { // do nothing } @Override public void onError(Throwable e) { //do something with the error } @Override public void onNext(List apiNewsDatum) { //do something with the parsed data } }); 

apiService est mon object RetrofitServiceProvider.

BTW: J’utilise Java 8 donc beaucoup d’expressions lambda sont à l’intérieur du code.

Je veux juste partager ma version. Il utilise la méthode rxJava retryWhen. Ma version réessaie la connexion tous les N = 15 s et émet presque immédiatement une nouvelle tentative lorsque la connexion Internet est rétablie.

 public class RetryWithDelayOrInternet implements Function, Flowable> { public static boolean isInternetUp; private int retryCount; @Override public Flowable apply(final Flowable 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()); }} 

Et vous devriez l’utiliser avant .subscribe comme ceci:

 .retryWhen(new RetryWithDelayOrInternet()) 

Vous devez modifier manuellement le champ 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(); }}