Scheduling recursive handlers from the IntentService to repeat HTTP calls

I am trying to implement exponential shutdown for retrying failed HTTP calls by scheduling a thread with handler.postDelayed(...)

each time my request fails. The problem is I am doing this from an IntentService that dies after the first thread is scheduled, so the handler cannot call itself. I am getting the following error:

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

      

My class with IntentService:

@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 working? What would be the best / cleanest way to schedule my HTTP exponential rollback attempts?

+3


source to share


1 answer


The main benefit of using it IntentService

is that it handles all background threads for you inside this method onHandleIntent(Intent intent)

. In this case, there is no reason to control the handler.

You can get close to this here by using AlarmManager

to schedule an intent to be delivered to your service. You must retain the repeat information in the intention to be delivered.

I think something like this:

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

      



It simply allows the IntentService to use a handle making the call in the background, and then assigns the intent to re-forward every time it fails, adds additional functionality to it, how many times it has been retried and how long the last retry delay was.

Note. If you are trying to send multiple intents to this service and more than one refused and must be migrated using AlarmManager

, only the last intent will be sent if the intents are considered equal according to Intent.filterEquals(Intent intent)

. If your intentions are identical, except for the additions attached to it, this will be a problem and you must use a unique request code for each scheduled assignment when you create PendingIntent

. Something like this:

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

      

My guess is that you could use your general settings to store the request code, which grows every time you have to schedule a retry.

+4


source







All Articles