Guaranteeing intent delivery (and redelivery) to your service

For my latest project I need to process some data and send it to a remote server. This was a perfect use case for an IntentService. I can just bundle my data as an extra, and start my service.

My biggest issue with an IntentService is that if it fails to start, I have essentially no way of knowing it failed. So I wanted a way to guarantee (or at least a best effort) that my intent starts up and processes my data.

First an intro to the IntentService

An intent service is a special subclass of the Service class. With a standard service, you generally override the onStartCommand(Intent, int, int) method. More importantly, all methods in the standard Service class run on the main thread.

This is not ideal if you want to perform any calculations, disk access, or network access from within your service without managing your own threads.

With an IntentService you get a new method to override called onHandleIntent(Intent intent) that is called in a dedicated background thread.

Starting an Intent Service

To start an intent service and pass your data, you can create an intent, add extras, and start the service.

Intent intent = new Intent(context, RedeliveryService.class);
intent.putExtra("string_field", "data"); // your string data
intent.putExtra("boolean_field", true); // your boolean data

context.startService(intent); // send the intent and start your service

When your service is started and your onHandleIntent(Intent) is called, you can call getStringExtra/getBooleanExtra to get your data.

What happens if the app is killed or crashes?

Now is the tricky question. If the app is killed or the service crashes, your intent will not start and your data will be lost.

Setting up Intent redelivery

Luckily you can tell Android that you want your Intent redelivered to your service! In the constructor of your IntentService, make a call to setIntentRedelivery(boolean).

public RedeliveryService() {
	super(SERVICE_NAME);
	setIntentRedelivery(true); // tells android to redeliver the intents
}

With Intent redelivery enabled, Android will keep attempting to deliver your intent to your service until onHandleIntent(Intent) returns without crashing.

So the app is killed or crashes, when is the Intent redelivered?

Android redelivers intents to your service using exponential backoff timeouts. This means each time the intent is not delivered, the delay before trying again goes up exponentially. Here is a sample log output.

W/ActivityManager: Scheduling restart of crashed service .RedeliveryService in 16896ms
W/ActivityManager: Scheduling restart of crashed service .RedeliveryService in 67584ms
W/ActivityManager: Scheduling restart of crashed service .RedeliveryService in 270336ms

This also works for crashes, but the interesting part is, the intent will no longer be delivered after your app crashes 2 times. I couldn’t find this documented anywhere, but after testing on API 19 this is the case.

It doesn’t matter how many attempts at redelivering have been made, it just keeps a running count of crashes and stops trying after 2.

Redelivery Overview

The system will keep redelivering (with an exponential delay) when the app is killed. The app will redeliver after 1 crash (with an exponential delay). Once the service crashes twice, redelivery stops.

For example you send your intent to your service

  1. The app is killed by the system. Android redelivers intent
  2. The service crashes Android redelivers intent
  3. The app is killed by the system again Android redelivers intent
  4. The app is killed by the user Android redelivers intent
  5. The service crashes for a second time Android gives up

Summary

The intent service has a pretty robust and well designed redelivery system. The biggest issue is that there is next to no documentation on the actual behavior of redelivery.

I put up a sample project on github where you can test out the redelivery. Simply click the button, and kill the app. Watch the intent get delivered again until the RuntimeException is thrown twice.