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 OkHttp semble avoir des tentatives silencieuses. Je ne vois pas cela se produire sur aucune de mes connexions (HTTP ou HTTPS). Comment configurer les tentatives avec okclient?
Pour l'instant, j'attrape les exceptions et réessaye en maintenant une variable de compteur.
Pour la mise à niveau 1.x;
Vous pouvez utiliser Interceptors . 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 en créant RestAdapter.
new RestAdapter.Builder()
.setEndpoint(API_URL)
.setRequestInterceptor(requestInterceptor)
.setClient(new OkClient(client))
.build()
.create(Adapter.class);
Pour la modification 2.x;
Vous pouvez utiliser Call.clone () method pour cloner une requête 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 renvoyer des observables sur des appels de repos. Sur Oberservables, vous pouvez simplement appeler retry(count)
pour vous réabonner à l’observable lorsqu’il émet une erreur.
Vous devez définir l'appel dans votre interface comme suit:
@GET("/data.json")
Observable<DataResponse> fetchSomeData();
Ensuite, vous pouvez vous abonner à cet 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<DataResponse>, Action1<Throwable>
// Here you can define what to do with the results
J'ai eu le même problème que toi et c'était en fait ma solution. RxJava est une bibliothèque vraiment 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 corriger.
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 ça aide. Cordialement.
Courtoisie à la réponse du haut, 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){
// 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;
}
}
}
J'ai trouvé que le chemin (intercepteur OKHttpClient) fourni par Sinan Kozak ne fonctionnait pas lorsque la connexion http échouait, rien ne concernait encore la réponse HTTP.
Donc, j'utilise un autre moyen pour accrocher l'objet Observable, appelez .retryWhen dessus . Aussi, j'ai ajouté retryCount limit.
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;
Ensuite
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);
}
}));
}
};
}
};
Ensuite ajouter ou remplacer
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
avec
.addCallAdapterFactory(newCallAdaptorFactory)
Par exemple:
return new Retrofit
.Builder()
.baseUrl(baseUrl)
.client(okClient)
.addCallAdapterFactory(newCallAdaptorFactory)
.addConverterFactory(JacksonConverterFactory.create(objectMapper));
Remarque: pour des raisons de simplicité, je traite simplement le code HTTP> 404 comme une nouvelle tentative, veuillez le modifier vous-même.
De plus, si la réponse http est 200, rx.retryWhen
ne sera pas appelé si vous souhaitez vérifier une telle réponse, vous pouvez ajouter rx.subscribeOn(...throw error...
avant .retryWhen.
Je suis d’avis que vous ne devriez pas mélanger la manipulation des API (effectuée par retrofit/okhttp) avec les tentatives. Les mécanismes de relance sont plus orthogonaux et peuvent également être utilisés dans de nombreux autres contextes. J'utilise donc Retrofit/OkHTTP pour tous les appels d'API et le traitement des demandes/réponses, et j'introduis une autre couche au-dessus, pour réessayer l'appel d'API.
Dans mon expérience limitée de Java jusqu'à présent, j'ai trouvé que la bibliothèque Failsafe de jhlaterman (github: jhalterman/Failafe ) était une bibliothèque très polyvalente pour gérer proprement de nombreuses situations de "tentatives". À titre d’exemple, voici comment je l’utiliserais avec un rétrofit instancié mySimpleService, pour l’authentification -
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;
});
Le code ci-dessus intercepte les exceptions de socket, les erreurs de connexion, les échecs d'assertion et les tentatives au maximum 3 fois, avec un retour exponentiel. Il vous permet également de personnaliser le comportement lors de nouvelles tentatives et vous permet également de spécifier un repli. Il est tout à fait configurable et peut s’adapter à la plupart des situations de tentative.
N'hésitez pas à consulter la documentation de la bibliothèque car elle propose de nombreux autres avantages, à part de nouvelles tentatives.
Pour ceux qui préfèrent un intercepteur pour traiter la question des nouvelles tentatives - En s'inspirant de la réponse de Sinan, voici le projet d'intercepteur que je propose, qui inclut à la fois le nombre de tentatives et le délai de désactivation, et ne tente que les tentatives lorsque le réseau est disponible n'a pas été annulé . (traite uniquement des exceptions IO (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 (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;
}
});
Je veux juste partager ma version. Il utilise la méthode retryWhen de rxJava. Ma version tente à nouveau la connexion toutes les N = 15 secondes et émet presque immédiatement une nouvelle tentative lorsque la connexion Internet est rétablie.
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());
}}
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();
}}
Une solution qui a fonctionné pour moi sur OkHttp 3.9.1 (en considérant d’autres réponses à cette question):
@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;
}
Il semble qu'il sera présent dans la version 2.0 de la spécification API: https://github.com/square/retrofit/issues/297 . À l'heure actuelle, la meilleure solution semble être une exception à la capture. et réessayez manuellement.
J'ai beaucoup joué avec ce problème en essayant de trouver le meilleur moyen de réessayer les demandes de modification. J'utilise Retrofit 2 et ma solution est donc 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é le fait que la méthode de nouvelle tentative doit être appelée avant la méthode .subscribe (onComplete, onError). C'est très important sinon la requête ne serait pas rejouée comme @pocmo mentionné dans la réponse @joluet. Voici mon exemple:
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 est mon objet RetrofitServiceProvider.
BTW: J'utilise Java 8, donc beaucoup d'expressions lambda sont dans le code.
Comme indiqué dans docs , il serait préférable d’utiliser les authentificateurs cuits au four, par exemple: Client final privé 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());
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());
}