Подтвердить что ты не робот

Повторная попытка запроса с использованием Retrofit 2

Как добавить функцию повтора для запросов, отправленных Retrofit 2. Что-то вроде:

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

        @Override
        public void onFailure(Throwable t) {
            ...
        }
    }).retryOnFailure(5 /* times */);
4b9b3361

Ответ 1

Я, наконец, сделал что-то вроде этого, для всех, кого это интересует:

1

Сначала я сделал абстрактный класс 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);
    }
}

Используя этот класс, я могу сделать что-то вроде этого:

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

2

Это не вполне удовлетворительно, потому что я должен пройти один и тот же serviceCall дважды. Это может смутить, поскольку можно подумать, что второй serviceCall (который входит в конструктор CallbackWithRetry) должен или может быть чем-то отличным от первого (который мы вызываем на нем методом enqueue)

Итак, я реализовал вспомогательный класс 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);
            }
        });
    }

}

И я могу использовать его следующим образом:

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

С этим я должен пройти стандартный метод Callback to enqueueWithRetry, и это заставляет меня реализовать onFailure (хотя в предыдущем методе я могу реализовать его тоже)

Так вот как я решил проблему. Любое предложение для лучшего дизайна было бы оценено.

Ответ 2

Я сделал пользовательскую реализацию интерфейса обратного вызова, вы можете в значительной степени использовать его вместо первоначального обратного вызова. Если вызов успешный, вызывается метод onResponse(). Если после повторной попытки для набора количества повторений вызов не выполняется, вызывается onFailedAfterRetry().

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

Ответ 4

Я сделал что-то очень похожее на Ашкана Сарлака, но так как Retrofit 2.1 передает Call<T> в метод onFailure, вы можете упростить один абстрактный класс CallbackWithRetry<T>. См:

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

Ответ 5

Ответ Ашкана-Сарлака отлично работает, и я просто пытаюсь сделать это в курсе.

Из дооснащения 2.1

onFailure(Throwable t) 

Изменить на

onFailure(Call<T> call, Throwable t)

Так что теперь это так просто. Просто создайте CallbackWithRetry.java вот так

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

Все это! Вы можете просто использовать это так

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

Ответ 6

Я думаю, что для Android нам не нужно идти на модернизацию для этого. Мы можем использовать Workmanager (который предопределяет Android API). Мы можем использовать "ListenableWorker.Result.SUCCESS", "ListenableWorker.Result.RETRY" и т.д. И достичь вышеуказанных целей.

Ответ 7

С дооснащением 2.5

Теперь можно сделать асинхронные синхронизирующие вызовы через java.util.concurrent.CompletableFuture, код ожидает его завершения, что очень приятно.

Здесь суть с рабочим решением.