I have made the following system to automatically retry network calls if some exception is thrown. (Earlier posted on stackOverFlow, solution inspired by what @Boris answered)
public static <T> Optional<T> autoRetry(@NonNull final DoWork<T> task, @NonNull final Optional<Predicate<T>> resultRejector) {
for (int retry = 0; retry <= StaticData.NETWORK_RETRY; ++retry) {
try {
Thread.sleep(retry * StaticData.NETWORK_CALL_WAIT);
final T resultAfterWork = task.doWork();
/**
* If the result was not
* desirable we RETRY.
*/
if(resultRejector.isPresent() && resultRejector.get().apply(resultAfterWork))
continue;
/**
* Else we return
*/
return Optional.fromNullable(resultAfterWork);
} catch (InterruptedException | UnknownHostException e) {
e.printStackTrace(); //To be replaced by proper logging
return Optional.absent();
} catch (IOException e) {
e.printStackTrace(); //To be replaced by proper logging
}
}
return Optional.absent();
}
- I take care to not retry after InterruptedException & UnknownHostException.
- I retry 5 times. After each failure I perform an exponential back off, starting from 300ms going upto 1500ms.
- The predicate is used to verify the validity of the expected result.
The DoWork class :
public abstract class DoWork<T> {
protected abstract T doWork() throws IOException;
}
I did not use Supplier because I need to throw an IOException.
Here is an example of how I use it. (this example updates the GCM Id)
//Context is android specific stuff, basically if app dies, reference will return null.
public static boolean updateGCM(final long id, final WeakReference<Context> reference) {
final String regId = autoRetry(new DoWork<String>() {
@Override
protected String doWork() throws IOException {
final Context context = reference.get();
if(context == null)
return "QUIT";
return GoogleCloudMessaging.getInstance(context)
.register("XXXXXXXXXX");
}
}, Optional.<Predicate<String>>of(new Predicate<String>() {
@Override
public boolean apply(@Nullable String input) {
return TextUtils.isEmpty(input); //I need to retry if the returned GCM ID was null/empty
}
})).orNull();
//Context becomes null when the application is exited
//If retires failed or application exited, we return
if(TextUtils.isEmpty(regId) || regId.equals("QUIT"))
return false;
Log.i("Ayush", "Uploading newGcmId to server");
final Boolean result = autoRetry(new DoWork<Boolean>() {
@Override
protected Boolean doWork() throws IOException {
//if everything is fine, send id to server
return true;
}
}, Optional.<Predicate<Boolean>>absent()).orNull(); //No need of predicate hence send absent
return !(result == null || !result);
}
- So on a scale of 1 to 10 how professional is this approach ? :)
- Also I do not want to retry if the network is genuinely failing, like UnknownHostException, or something bad happens which wont get resolved with retrying. Any possibility of improving exception handling ?