Thursday, September 20, 2012

IntentService and inter-process communication

Most likely you are familiar with Android's AsyncTask and you use it whenever you have some work that needs to be done in a background. This way the main thread that is responsible for user interface is not kept busy and our application looks snappy.

In case you want to expose your background task to other applications, you will create a Service. Yes, you could expose your Activity that wraps around the AsyncTask, but there is no point doing that since the work needs to be done in background anyways.

We have two options now: our service could extend either Service or IntentService class.

If you decide to extend the Service class be aware that your service when started will still run on your application's main thread, i.e. it will block your user interface. So your service has to create a new thread that will do all the work. Also be aware that your service might receive a new request while the old one is still running (you are exposing your service to the outside world so you have no control over the frequency of calls to it). In that case you have to take care that your threads do not run over each other, meaning - you will be extra careful with the data that is shared between the threads or you will implement some kind of working queue. This is becoming painful already ...

However, when our service extends IntentService class a lot of dirty work is done for us. Our work will be executed on a worker thread and not on a main one, all requests will be queued and served on at the time, and finally our service will shut itself when all requests have been handled. Nice!

Now let's say that our service really has a work to do and that we would like to provide some kind of progress bar in our activity that started the service. For that kind of thing we need some kind of communication between the activity and the service. The usual way to achieve that is using Bound Services. Bound services are started with bindService and not with startService call, when the service is bound you can get a messenger back that is used for activity-to-service communication and your activity can send back to service another messenger that is used for service-to-activity communication. Everything is great except that IntentService does not support the binding.

Well, the story is not over. We can solve this by passing the messenger to the service as Intent's extra. Here is the code sample, together with message handler:


private static class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
	System.out.println("Message received!");
    }
}
	
private void startIntentService() {
    Messenger messenger = new Messenger(new IncomingHandler());
    Intent intent = new Intent("com.example.service.ExampleService");
    intent.putExtra("com.example.service.Messenger", messenger);
    startService(intent);
}	

And here is (almost) the full source code of simple service that just sleeps for 25 seconds and each 5 seconds sends the message back:

public class ExampleService extends IntentService {
	
public ExampleService() {
    super("ExampleService");
}
	
@Override
protected void onHandleIntent(Intent intent) {
		
    Messenger outMessenger = 
	intent.getParcelableExtra("com.example.service.Messenger");

    for (int i = 0; i < 5; i++) {
	SystemClock.sleep(5000);
	if (outMessenger != null) {
	    try {
		outMessenger.send(Message.obtain());
	    } catch (RemoteException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	}
    }
}
}

As simple as that. Just do not forget to declare your service in it's manifest file like this:

<application ...>
...
<service android:name=".ExampleService">
    <intent-filter>
	<action android:name="com.example.service.ExampleService" />
    </intent-filter>
</service>
</application>