-
-
Save ashif-ismail/de09b49c2ba9371c264c747c70bda445 to your computer and use it in GitHub Desktop.
Call retrying with Retrofit 2.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.api; | |
import java.util.Map; | |
import retrofit.Call; | |
import retrofit.http.Body; | |
import retrofit.http.GET; | |
import retrofit.http.POST; | |
public interface Api { | |
@Retry(1) | |
@GET("action") | |
Call getAction(); | |
@Retry | |
@POST("action") | |
Call postAction(@Body Map<String, Object> requestBody); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.api; | |
import com.squareup.okhttp.OkHttpClient; | |
import retrofit.GsonConverterFactory; | |
import retrofit.Retrofit; | |
public class ApiClient { | |
public final Api api; | |
public ApiClient(OkHttpClient client) { | |
final Retrofit retrofit = new Retrofit.Builder() | |
.addCallAdapterFactory(RetryCallAdapterFactory.create()) | |
.baseUrl("https://api.example.com") | |
.build(); | |
api = retrofit.create(Api.class); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.api; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import static java.lang.annotation.ElementType.METHOD; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
/** | |
* Makes the Call retry on failure | |
*/ | |
@Documented | |
@Target(METHOD) | |
@Retention(RUNTIME) | |
public @interface Retry { | |
int value() default 3; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.api; | |
import java.io.IOException; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Type; | |
import java.util.Random; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.ScheduledExecutorService; | |
import java.util.concurrent.TimeUnit; | |
import retrofit.Call; | |
import retrofit.CallAdapter; | |
import retrofit.Callback; | |
import retrofit.Response; | |
import retrofit.Retrofit; | |
/** | |
* Retries calls marked with {@link Retry}. | |
*/ | |
public class RetryCallAdapterFactory implements CallAdapter.Factory { | |
private final ScheduledExecutorService mExecutor; | |
private RetryCallAdapterFactory() { | |
mExecutor = Executors.newScheduledThreadPool(1); | |
} | |
public static RetryCallAdapterFactory create() { | |
return new RetryCallAdapterFactory(); | |
} | |
@Override | |
public CallAdapter<?> get(final Type returnType, Annotation[] annotations, Retrofit retrofit) { | |
boolean hasRetryAnnotation = false; | |
int value = 0; | |
for (Annotation annotation : annotations) { | |
if (annotation instanceof Retry) { | |
hasRetryAnnotation = true; | |
value = ((Retry) annotation).value(); | |
} | |
} | |
final boolean shouldRetryCall = hasRetryAnnotation; | |
final int maxRetries = value; | |
final CallAdapter<?> delegate = retrofit.nextCallAdapter(this, returnType, annotations); | |
return new CallAdapter<Object>() { | |
@Override | |
public Type responseType() { | |
return delegate.responseType(); | |
} | |
@Override | |
public <R> Object adapt(Call<R> call) { | |
return delegate.adapt(shouldRetryCall ? new RetryingCall<>(call, mExecutor, maxRetries) : call); | |
} | |
}; | |
} | |
static final class RetryingCall<T> implements Call<T> { | |
private final Call<T> mDelegate; | |
private final ScheduledExecutorService mExecutor; | |
private final int mMaxRetries; | |
public RetryingCall(Call<T> delegate, ScheduledExecutorService executor, int maxRetries) { | |
mDelegate = delegate; | |
mExecutor = executor; | |
mMaxRetries = maxRetries; | |
} | |
@Override | |
public Response<T> execute() throws IOException { | |
return mDelegate.execute(); | |
} | |
@Override | |
public void enqueue(Callback<T> callback) { | |
mDelegate.enqueue(new RetryingCallback<>(mDelegate, callback, mExecutor, mMaxRetries)); | |
} | |
@Override | |
public void cancel() { | |
mDelegate.cancel(); | |
} | |
@SuppressWarnings("CloneDoesntCallSuperClone" /* Performing deep clone */) | |
@Override | |
public Call<T> clone() { | |
return new RetryingCall<>(mDelegate.clone(), mExecutor, mMaxRetries); | |
} | |
} | |
// Exponential backoff approach from https://developers.google.com/drive/web/handle-errors | |
static final class RetryingCallback<T> implements Callback<T> { | |
private static Random random = new Random(); | |
private final int mMaxRetries; | |
private final Call<T> mCall; | |
private final Callback<T> mDelegate; | |
private final ScheduledExecutorService mExecutor; | |
private final int mRetries; | |
RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries) { | |
this(call, delegate, executor, maxRetries, 0); | |
} | |
RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries, int retries) { | |
mCall = call; | |
mDelegate = delegate; | |
mExecutor = executor; | |
mMaxRetries = maxRetries; | |
mRetries = retries; | |
} | |
@Override | |
public void onResponse(Response<T> response, Retrofit retrofit) { | |
mDelegate.onResponse(response, retrofit); | |
} | |
@Override | |
public void onFailure(Throwable throwable) { | |
// Retry failed request | |
if (mRetries < mMaxRetries) { | |
retryCall(); | |
} else { | |
mDelegate.onFailure(new TimeoutError(throwable)); | |
} | |
} | |
private void retryCall() { | |
mExecutor.schedule(new Runnable() { | |
@Override | |
public void run() { | |
final Call<T> call = mCall.clone(); | |
call.enqueue(new RetryingCallback<>(call, mDelegate, mExecutor, mMaxRetries, mRetries + 1)); | |
} | |
}, (1 << mRetries) * 1000 + random.nextInt(1001), TimeUnit.MILLISECONDS); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.api; | |
import java.io.IOException; | |
public class TimeoutError extends IOException { | |
private static final long serialVersionUID = -6469766654369165864L; | |
public TimeoutError() { | |
super(); | |
} | |
public TimeoutError(Throwable cause) { | |
super(cause); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment