webentwicklung-frage-antwort-db.com.de

Wiederholen der Anforderung mit Retrofit 2

Wie kann ich den von Retrofit 2 library gesendeten Anforderungen eine Wiederholungsfunktion hinzufügen? So etwas wie:

service.listItems().enqueue(new Callback<List<Item>>() {
        @Override
        public void onResponse(Response<List<Item>> response) {
            ...
        }

        @Override
        public void onFailure(Throwable t) {
            ...
        }
    }).retryOnFailure(5 /* times */);
21
Ashkan Sarlak

Ich habe endlich so etwas gemacht, für alle Interessierten:

1

Zuerst machte ich eine abstrakte Klasse 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);
    }
}

Mit dieser Klasse kann ich so etwas machen:

serviceCall.enqueue(new CallbackWithRetry<List<Album>>(serviceCall) {
    @Override
    public void onResponse(Response<List<Album>> response) {
        ...
    }
});

2

Dies ist nicht völlig zufriedenstellend, da ich dieselbe serviceCall zweimal übergeben muss. Dies kann verwirrend sein, da man denken kann, dass das zweite serviceCall (das in den Konstruktor von CallbackWithRetry geht) etwas anderes sein sollte als das erste (das wir die enqueue-Methode darauf aufrufen)

Also habe ich eine Hilfsklasse CallUtils implementiert:

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

}

Und ich kann es so verwenden:

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

Damit muss ich eine Standard Callback an die enqueueWithRetry-Methode übergeben und es lässt mich onFailure implementieren (obwohl ich es in der vorherigen Methode auch implementieren kann)

So habe ich das Problem gelöst. Jeder Vorschlag für ein besseres Design wäre dankbar. 

49
Ashkan Sarlak

Ich habe eine benutzerdefinierte Implementierung der Callback-Schnittstelle vorgenommen. Sie können sie anstelle des ursprünglichen Rückrufs verwenden. Wenn der Aufruf erfolgreich ist, wird die onResponse () -Methode aufgerufen. Wenn der Wiederholungsversuch für eine bestimmte Anzahl von Wiederholungen fehlschlägt, wird onFailedAfterRetry () aufgerufen.

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

7
milechainsaw

Gehen Sie zu RxJava Observable und rufen Sie retry () Doc: https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators

3
guillaume_fr

Ich habe etwas Ähnliches wie Ashkan Sarlak gemacht, aber da Retrofit 2.1 den Call<T> in die onFailure-Methode übergibt, können Sie die abstrakte Klasse CallbackWithRetry<T> vereinfachen. Sehen:

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

ashkan-sarlak Antwort klappt super und ich versuche nur, es auf den neuesten Stand zu bringen.

Von nachrüsten 2.1

onFailure(Throwable t) 

Ändern

onFailure(Call<T> call, Throwable t)

Das macht es jetzt so einfach. Erstellen Sie einfach CallbackWithRetry.Java so

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

Das ist alles! Sie können es einfach so verwenden

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

Ich denke, dass wir für Android keine Nachrüstung benötigen. Wir können Workmanager verwenden (der Android-API vordefiniert) ... _ und die oben genannten Ziele erreichen.

0
SIVAKUMAR.J

Mit Nachrüstung 2.5

Jetzt ist es möglich, async-Synchronisierungsaufrufe über Java.util.concurrent.CompletableFuture durchzuführen, der Code wartet auf seine Fertigstellung, was sehr schön ist.

Hier ist ein Gist mit einer funktionierenden Lösung.

0
DTodt