Sunday, February 3, 2008

Double life of a service

Earlier (with much less experience in Android services) I wrote about the two kinds of services behind the android.app.Service abstraction: a long-running service whose lifecycle is independent of the activity that started it (Context.startService) and a service bound by other services and activities by its AIDL interface (Context.bindService). The lifecycle of this second type of service depends on the lifecycles of the entities that bind it, if the service has no more bound client, the application manager is free to destroy it. Meanwhile, the first type of service exists until the clients that started the service stop it or until the service stops itself. In extremely low resource conditions the application manager may shut down the first type of service too but this is rare.

I don't know how about you but the difference between the two types always seemed to be unnatural for me. For starter, I can pretty much imagine a number of useful and practical services that are long-running and clients want to communicate with them at the same time. For example a P2P module would be such a service; it would run in the background doing discovery-related or other long-running tasks and occassionally client applications would connect to it to accomplish P2P operations.

It turns out that this difference is artificial. Android services can exhibit both properties: they can be long-running and can be bound and unbound with AIDL interface operations. This is possible because there is only one service instance, independently of the method the service was started with. I discovered it recently and as there was a topic about it in the Android groups, I decided to quickly put together an example program about it.

You can download the example program from here.

You can download the example program updated for SDK 1.5 from here.

Our example is simple. There is a service that holds a counter and can return that counter by means of an AIDL interface. The service need to be bound by Context.bindService() to obtain client-side interface stub. This is not very useful, however, because the counter does not change, it needs to count forward. In order to do that, we start the same service with Context.startService(). It turns out that the onStart() that follows is invoked on the same service instance. Hence we have a service that conforms to both types: it is long-running and serves interface invocations at the same time.

Simple? I thought so. I implemented a version of the example program and it worked flawlessy except that the service could not be stopped. It turns out that a dual service like that can be stopped with Context.stopService() call only if both lifecycles models allow it. This means that stopService() has no effect on a bound service!

Let's take an example. The client activity allows binding/unbinding, starting/stopping the service manually.



Now let's see what happens if the service is bound, unbound then started and stopped.

D/DUALSERVICECLIENT( 568): bindService()
D/ActivityThread( 568): Creating service aexp.dualservice.DualService
D/DUALSERVICE( 568): onCreate
D/DUALSERVICECLIENT( 568): onServiceConnected
I/ActivityManager( 465): Stopping service: {aexp.dualservice/aexp.dualservice.DualService}
D/DUALSERVICECLIENT( 568): unbindService()
D/ActivityThread( 568): Stopping service aexp.dualservice.DualService@40044928
D/DUALSERVICE( 568): onDestroy
D/DUALSERVICECLIENT( 568): startService()
D/ActivityThread( 568): Creating service aexp.dualservice.DualService
D/DUALSERVICE( 568): onCreate
D/DUALSERVICE( 568): onStart
I/ActivityManager( 465): Stopping service: {aexp.dualservice/aexp.dualservice.DualService}
D/DUALSERVICECLIENT( 568): stopService()
D/ActivityThread( 568): Stopping service aexp.dualservice.DualService@40048cd0
D/DUALSERVICE( 568): onDestroy

The service lifecycles work nicely: when the service is bound, its onCreate is called, then it is destroyed (onDestroy()) when it is unbound. The same goes for startService-stopService except that onStart() is called after onCreate(), as it should happen.

Now let's see what happens if the sequence is bind-start-stop-unbind!

D/DUALSERVICECLIENT( 568): bindService()
D/ActivityThread( 568): Creating service aexp.dualservice.DualService
D/DUALSERVICE( 568): onCreate
D/DUALSERVICECLIENT( 568): onServiceConnected
D/DUALSERVICECLIENT( 568): startService()
D/DUALSERVICE( 568): onStart
D/DUALSERVICECLIENT( 568): stopService()
I/ActivityManager( 465): Stopping service: {aexp.dualservice/aexp.dualservice.DualService}
D/DUALSERVICECLIENT( 568): unbindService()
D/ActivityThread( 568): Stopping service aexp.dualservice.DualService@4004a430
D/DUALSERVICE( 568): onDestroy


ActivityManager reports twice that the service was stopped. Of these two, only the second one is real (leads to the invocation of onDestroy()). As the service abstraction has no onStop() method (that would be counterpart of onStart()), the service is not notified that it was stopped. You can examine that with getting the counter: it continues counting after the service was stopped.

This is annoying enough but a method like stopCounting() on the AIDL interface would solve the problem. My opinion, however, is that the lack of service stop notification will cause problems with less tricky long-running services too. For example a voice recorder service will not be able to correctly free the resources (e.g. close the output file correctly) if it is just stopped by one of its clients or if the ActivityManager shuts it down due to low resource condition. I don't know how hard it would be ti implement stop notification callback (finalizers are the toughest kind of callbacks) but this enhancement was hacked into every application model that missed it originally (like Java applications, for example).

27 comments:

Anonymous said...

I'm testing your example Double Lige in the SDK m5, I start the service, bind the service but whe I try to invoke it, i have any problem, I get a null pointer, I think the my problem is in:

private CounterServiceConnection conn;

class CounterServiceConnection implements ServiceConnection {


public void onServiceConnected(ComponentName className,
IBinder boundService ) {
Log.d( LOG_TAG,"onServiceConnected 1" );
counterService = ICounterService.Stub.asInterface((IBinder)boundService);
Log.d( LOG_TAG,"onServiceConnected" );
}

public void onServiceDisconnected(ComponentName className) {
counterService = null;
Log.d( LOG_TAG,"onServiceDisconnected" );
updateServiceStatus();
}
};

Charlotte said...

I also have the samne problem
bindService( i, conn, Context.BIND_AUTO_CREATE); seems successful, the return value is true.

Anonymous said...

In testing to be sure that service has been absolutely stopped, I commented out the sections where you set the connection and and service objects to null.

I unbinded the service and stopped it, then tried to invoke it. I found that even though the service should be stopped and not running, I was still able to receive numbers back.

Am I missing something, or is the null assignment just covering up a service that isn't really stopped?

Anonymous said...

SDK has updated, so I think you should update your example code. :)

Anonymous said...

is there an updated example, that runs with 1.5 SDK?

Gabor Paller said...

"is there an updated example, that runs with 1.5 SDK?"

Yes, there is. ;-)

Anonymous said...

where ? :D

Gabor Paller said...

In the blog text, there is a new example code link under the old one.

eithu said...

thanks a lot. It works well on both 1.5 and 1.6 :)
Thank you so much!

Max Pierson said...

I kept getting a null pointer exception whenever I tried to call any of my service methods, and finally I realized that calling bindService() means that Android will connect to your service at some point, whenever it has a moment.

So you need some time between binding and when you start calling methods, if anyone else is having issues.

Thanks for the great tutorial!

Gabor Paller said...

Max, check out the CounterServiceConnection.onServiceConnected method. You can use your service after the Android framework calls back this method.

Parag Patel said...

Sorry but not able to download 1.5 specific example.

Gabor Paller said...

"Sorry but not able to download 1.5 specific example."

I have just tried (from the UK), it works for me.

Jeremy R. Fishman said...

Thanks, this was very helpful!

Blake said...

Wow ty so much, this really helped me understand what the heck is going on with a service and how to set one up. Sure the android dev manual tells you what a service is, but not how to use it.

naresht said...

Hi .. Thanks allot for sharing this demo. It helped me to understand service life cycle. Keepup your good work

réda said...

Thanks a lot for comment, hihi

Thanks max pierson ,

"So you need some time between binding and when you start calling methods, if anyone else is having issues."


boolean bo = bindService(intent2, mConnection, BIND_AUTO_CREATE);
serviceBinder.stopSelf();

serviceBinder here is null, you need to wait , or use it in onServiceConnected

Anonymous said...

Thank you.. and love you.. :p

SuVish said...

Thanks dude....the documentation on services don't clearly spill this out...thanks a bunch!

sasha said...

nice information

Anonymous said...

But, how can we make it wait some time between the binding and the calling? I am terribly lost on this!

developer code said...

it's not working.when i import this project in my eclipse then generate a error that is not a project.

John said...

What happens when i start the running service again..?

Gabor Paller said...

John, onStartCommand will be called again, see documentation here.

tournote said...

Your examples are very usefull and simple, thank you very much

snicolas said...

In the case the sequence is

start - bind - unbind - whatever

you got a onDestroy method invocation when unbinding the service. Then the service is recreated and the instance last for as long as stop is invoked.

This a real problem. The service is not expected to die after unbing it if it has been started. This seems to be a bug in Android. The only workaround I could find is to set the service to foreground.

Unknown said...
This comment has been removed by a blog administrator.