Comment ajouter une fonctionnalité de nouvelle tentative aux demandes envoyées par Retrofit 2 library. Quelque chose comme:
service.listItems().enqueue(new Callback<List<Item>>() {
@Override
public void onResponse(Response<List<Item>> response) {
...
}
@Override
public void onFailure(Throwable t) {
...
}
}).retryOnFailure(5 /* times */);
J'ai finalement fait quelque chose comme ça, pour ceux qui sont intéressés:
J'ai d'abord créé une classe abstraite CallbackWithRetry
public abstract class CallbackWithRetry<T> implements Callback<T> {
private static final int TOTAL_RETRIES = 3;
private static final String TAG = CallbackWithRetry.class.getSimpleName();
private final Call<T> call;
private int retryCount = 0;
public CallbackWithRetry(Call<T> call) {
this.call = call;
}
@Override
public void onFailure(Throwable t) {
Log.e(TAG, t.getLocalizedMessage());
if (retryCount++ < TOTAL_RETRIES) {
Log.v(TAG, "Retrying... (" + retryCount + " out of " + TOTAL_RETRIES + ")");
retry();
}
}
private void retry() {
call.clone().enqueue(this);
}
}
En utilisant cette classe, je peux faire quelque chose comme ceci:
serviceCall.enqueue(new CallbackWithRetry<List<Album>>(serviceCall) {
@Override
public void onResponse(Response<List<Album>> response) {
...
}
});
Ce n'est pas complètement satisfaisant car je dois passer la même serviceCall
deux fois. Cela peut être déroutant car on peut penser que la seconde serviceCall
(qui entre dans le constructeur de CallbackWithRetry
) devrait ou pourrait être quelque chose de différent du premier (que nous appelons la méthode enqueue
sur elle)
J'ai donc implémenté une classe d'assistance CallUtils
:
public class CallUtils {
public static <T> void enqueueWithRetry(Call<T> call, final Callback<T> callback) {
call.enqueue(new CallbackWithRetry<T>(call) {
@Override
public void onResponse(Response<T> response) {
callback.onResponse(response);
}
@Override
public void onFailure(Throwable t) {
super.onFailure(t);
callback.onFailure(t);
}
});
}
}
Et je peux l'utiliser comme ça:
CallUtils.enqueueWithRetry(serviceCall, new Callback<List<Album>>() {
@Override
public void onResponse(Response<List<Album>> response) {
...
}
@Override
public void onFailure(Throwable t) {
// Let the underlying method do the job of retrying.
}
});
Avec cela, je dois passer une méthode standard Callback
à enqueueWithRetry
et cela me permet d'implémenter onFailure
(bien que dans la méthode précédente, je puisse l'implémenter aussi)
Voilà comment j'ai résolu le problème. Toute suggestion pour un meilleur design serait appréciée.
J'ai créé une implémentation personnalisée de l'interface de rappel, vous pouvez très bien l'utiliser à la place du rappel d'origine. Si l'appel aboutit, la méthode onResponse () est appelée. Si après un nouvel essai pour un nombre défini de répétitions, l'appel échoue, onFailedAfterRetry () est appelé.
public abstract class BackoffCallback<T> implements Callback<T> {
private static final int RETRY_COUNT = 3;
/**
* Base retry delay for exponential backoff, in Milliseconds
*/
private static final double RETRY_DELAY = 300;
private int retryCount = 0;
@Override
public void onFailure(final Call<T> call, Throwable t) {
retryCount++;
if (retryCount <= RETRY_COUNT) {
int expDelay = (int) (RETRY_DELAY * Math.pow(2, Math.max(0, retryCount - 1)));
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
retry(call);
}
}, expDelay);
} else {
onFailedAfterRetry(t);
}
}
private void retry(Call<T> call) {
call.clone().enqueue(this);
}
public abstract void onFailedAfterRetry(Throwable t);
}
https://Gist.github.com/milechainsaw/811c1b583706da60417ed10d35d2808f
Allez avec RxJava Observable et appelez retry () Doc: https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
J'ai fait quelque chose d'assez similaire à Ashkan Sarlak, mais depuis que Retrofit 2.1 passe le Call<T>
dans la méthode onFailure
, vous pouvez simplifier une classe abstraite CallbackWithRetry<T>
. Voir:
public abstract class CallbackWithRetry<T> implements Callback<T> {
private static final String TAG = "CallbackWithRetry";
private int retryCount = 0;
private final Logger logger;
private final String requestName;
private final int retryAttempts;
protected CallbackWithRetry(@NonNull Logger logger, @NonNull String requestName, int retryAttempts) {
this.logger = logger;
this.requestName = requestName;
this.retryAttempts = retryAttempts;
}
@Override
public void onFailure(Call<T> call, Throwable t) {
if (retryCount < retryAttempts) {
logger.e(TAG, "Retrying ", requestName, "... (", retryCount, " out of ", retryAttempts, ")");
retry(call);
retryCount += 1;
} else {
logger.e(TAG, "Failed request ", requestName, " after ", retryAttempts, " attempts");
}
}
private void retry(Call<T> call) {
call.clone().enqueue(this);
}
}
ashkan-sarlak réponds que je travaille bien et que je veux juste le mettre à jour.
À partir de modernisation 2.1
onFailure(Throwable t)
Changer en
onFailure(Call<T> call, Throwable t)
Donc, cela le rend si facile maintenant. Créez simplement CallbackWithRetry.Java
comme ceci
public abstract class CallbackWithRetry<T> implements Callback<T> {
private static final int TOTAL_RETRIES = 3;
private static final String TAG = CallbackWithRetry.class.getSimpleName();
private int retryCount = 0;
@Override
public void onFailure(Call<T> call, Throwable t) {
Log.e(TAG, t.getLocalizedMessage());
if (retryCount++ < TOTAL_RETRIES) {
Log.v(TAG, "Retrying... (" + retryCount + " out of " + TOTAL_RETRIES + ")");
retry(call);
}
}
private void retry(Call<T> call) {
call.clone().enqueue(this);
}
}
C'est tout! vous pouvez simplement l'utiliser comme ça
call.enqueue(new CallbackWithRetry<someResponseClass>() {
@Override
public void onResponse(@NonNull Call<someResponseClass> call, @NonNull retrofit2.Response<someResponseClass> response) {
//do what you want
}
@Override
public void onFailure(@NonNull Call<someResponseClass> call, @NonNull Throwable t) {
super.onFailure(call,t);
//do some thing to show ui you trying
//or don't show! its optional
}
});
Je pense que pour Android, nous n’avons pas besoin de demander une adaptation ultérieure. Nous pouvons utiliser Workmanager (qui prédéfinit l’API Android) . Nous pouvons utiliser "ListenableWorker.Result.SUCCESS", "ListenableWorker.Result.RETRY", etc. et atteindre les objectifs ci-dessus.
Avec rattrapage 2.5
Maintenant, il est possible de faire des appels de synchronisation asynchrone via Java.util.concurrent.CompletableFuture, le code attend son achèvement, ce qui est très agréable.
Voici un Gist avec une solution de travail.