Java – Dispatch a recursive handler from IntentService to retry the http call

Dispatch a recursive handler from IntentService to retry the http call… here is a solution to the problem.

Dispatch a recursive handler from IntentService to retry the http call

Every time my request fails, I try to implement exponential backoff to retry the failed http call by scheduling a thread with handler.postDelayed(...). The problem is that I’m doing this from IntentService, which dies after scheduling the first thread, so the handler can’t call itself. I get the following error:

java.lang.IllegalStateException: Handler (android.os.Handler) {2f31b19b} sending message to a Handler on a dead thread

My IntentService class:

@Override
    protected void onHandleIntent(Intent intent) {

......

Handler handler = new Handler();
        HttpRunnable httpRunnable = new HttpRunnable(info, handler);
        handler.postDelayed(httpRunnable, 0);

}

My custom Runnable:

public class HttpRunnable implements Runnable {

private String info;
  private static final String TAG = "HttpRunnable";
  Handler handler = null;
  int maxTries = 10;
  int retryCount = 0;
  int retryDelay = 1000;  Set the first delay here which will increase exponentially with each retry

public HttpRunnable(String info, Handler handler) {
    this.info = info;
    this.handler = handler;
  }

@Override
  public void run() {
    try {
         Call my class which takes care of the http call
        ApiBridge.getInstance().makeHttpCall(info);
    } catch (Exception e) {
        Log.d(TAG, e.toString());
        if (maxTries > retryCount) {
        Log.d(TAG,"%nRetrying in " + retryDelay / 1000 + " seconds");
            retryCount++;
            handler.postDelayed(this, retryDelay);
            retryDelay = retryDelay * 2;
        }
    }
  }
}

Is there a way to keep my handler in the Activity state? What is the best/cleanest way to schedule my HTTP retries using exponential backoff?

Solution

The main advantage of using IntentService is that it handles all background threads for you in the onHandleIntent(Intent intent) method. In this case, you have no reason to manage the handler yourself.

You can use AlarmManager to schedule an intent to be sent to your service. You will keep the retry information in the intent to be delivered.

I thought so:

public class YourService extends IntentService {

private static final String EXTRA_FAILED_ATTEMPTS = "com.your.package.EXTRA_FAILED_ATTEMPTS";
    private static final String EXTRA_LAST_DELAY = "com.your.package.EXTRA_LAST_DELAY";
    private static final int MAX_RETRIES = 10;
    private static final int RETRY_DELAY = 1000;

public YourService() {
        super("YourService");
    }

@Override
    protected final void onHandleIntent(Intent intent) {

 Your other code obtaining your info string.

try {
             Make your http call.
            ApiBridge.getInstance().makeHttpCall(info);
        } catch (Exception e) {
             Get the number of previously failed attempts, and add one.
            int failedAttempts = intent.getIntExtra(EXTRA_FAILED_ATTEMPTS, 0) + 1;
             if we have failed less than the max retries, reschedule the intent
            if (failedAttempts < MAX_RETRIES) {
                 calculate the next delay
                int lastDelay = intent.getIntExtra(EXTRA_LAST_DELAY, 0);
                int thisDelay;
                if (lastDelay == 0) {
                    thisDelay = RETRY_DELAY;
                } else {
                    thisDelay = lastDelay * 2;
                }
                 update the intent with the latest retry info
                intent.putExtra(EXTRA_FAILED_ATTEMPTS, failedAttempts);
                intent.putExtra(EXTRA_LAST_DELAY, thisDelay);
                 get the alarm manager
                AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
                 make the pending intent
                PendingIntent pendingIntent = PendingIntent
                        .getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                 schedule the intent for future delivery
                alarmManager.set(AlarmManager.RTC_WAKEUP,
                        System.currentTimeMillis() + thisDelay, pendingIntent);
            }
        }
    }
}

This simply tells the IntentService you’re using to process the call in the background and then schedule the intent to be resent each time it fails, adding extra information to it like the number of retries and the length of the retry last time the delay was.

Note: If you attempt to send multiple intents to this service, but more than one fails and must be rescheduled using AlarmManager, only the latest intents will be delivered to Intent.filterEquals(Intent intent) if the intents are considered equal. This will be a problem if your intent is the same except for the extra content attached to it, and you must use a unique requestCode for each intent you want to reschedule when you create your PendingIntent . Something along these lines:

int requestCode = getNextRequestCode();
PendingIntent pendingIntent = PendingIntent
    .getService(getApplicationContext(), requestCode, intent, 0);

I guess you can use your shared preferences to store a request code that is incremented every time you have to schedule a retry.

Related Problems and Solutions