使用 Retrofit 2 重试请求

32

我该如何在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 */);
8个回答

65

如果有人感兴趣,我最近做了类似于这样的事情:

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传递给enqueueWithRetry方法,这使得我必须实现onFailure方法(尽管在之前的方法中我也可以实现它)。

因此,这就是我解决问题的方式。如果有更好的设计建议,欢迎提出。


1
在CallUtils中被覆盖的方法应该是:@Override public void onResponse(Response<T> response, Retrofit retrofit) { callback.onResponse(response, retrofit); } - Ninja Coding
我们如何可以从CallbackWithRetry类中更改baseUrl? - Ninja Coding
6
好的!顺便说一下,使用 Retrofit 2.1 版本之后,Callback.onFailure(Call<T> call, Throwable t) 方法已经包含了 "call" 参数了,不再需要使用 CallUtils - situee

14

我已经自定义了Callback接口的实现,您可以在原始回调的位置上使用它。如果调用成功,则会调用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


在我的自定义CallAdapter.Factory中实现此解决方案会导致RuntimeException(无法在未调用Looper.prepare()的线程内创建处理程序)。 - Granjero

5

ashkan-sarlak的答案非常好,我只是尝试使其更新。

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

3

3

使用 Retrofit 2.5

现在可以通过 java.util.concurrent.CompletableFuture 进行异步同步调用,代码等待其完成非常好。

这里有一个包含可工作解决方案的代码片段


3

我做了与Ashkan Sarlak相似的事情,但由于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);
  }
}

这个怎么使用? - behelit
还没有尝试过 - 但是您将调用 "call.enque(new CallBackWithRetry() { ... }" 匿名内部类继承了 "onFailure" 的默认实现。 - cljk

1
如果重试是可选的,那么解决此问题的另一个方法是:
public class CustomCallback<T> implements Callback<T> {
    @NonNull
    Callback<T> callback;

    private int retryCount = 0;
    private int maxRetry = 0;

    @EverythingIsNonNull
    public CustomCallback(Callback<T> callback) {
        this.callback = callback;
    }


    public CustomCallback<T> retryOnFailure(int nbRetry) {
        maxRetry = nbRetry;
        return this;
    }

    @EverythingIsNonNull
    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        callback.onResponse(call, response);
    }

    @EverythingIsNonNull
    @Override
    public void onFailure(Call<T> call, Throwable t) {
        if (maxRetry > retryCount) {
            retryCount++;
            call.clone().enqueue(this);
            return;
        }
        callback.onFailure(call, t);
    }
}

这样,您可以选择是否要重试:
//With retry
 myAPI.makeCall().enqueue(new CustomCallback<>(myCallback).retryOnFailure(3));
//Without
 myAPI.makeCall().enqueue(new CustomCallback<>(myCallback));

0

我认为对于安卓来说,我们不需要使用retrofit。我们可以利用Workmanager(预定义的安卓API)。 我们可以使用“ListenableWorker.Result.SUCCESS”,“ListenableWorker.Result.RETRY”等来实现上述目标。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接