Hello guys, it has been a while since the last time I posted something in here, I’ve been busy with some projects going on and honestly have been wanting to write about this topic for a while. I feel it’s one of those that since is so extensive, new developers have troubles grasping it (I know I had some problems understanding this at the beginning) and when trying to make some research you end up jumping back and forth in the documentation, so I would like to try to explain them as short and concise as I can.
What is a Service?
An Android Service is an application component capable of performing long-running operations, it doesn’t provide a UI and can be started (or bound) by other components, and continue running even when the user exits the application. They can be used to perform Inter-Process Communication between components of the same app running on different processes or even between independent applications.
As developers sometimes we want to perform some work which a) may be irrelevant for the user and probably doesn’t need to be aware of (like submitting error logs to our servers) or b) we want to continue the work even if the user exits the application (like a music app that continues playing a song after the user presses the home button) or c) both, in these cases other options to perform background work result insufficient for one reason or the other, Services provides a robust framework to achieve this, letting know the state of the work to other components in case of it being necessary.
Note that by default services run in the very same thread in which they are initiated. If you were to perform long-running operations you will need to handle all the threading by yourself or use an IntentService (more on this below).
Types of Services
We usually divide the services based on the way they are meant to be initiated because it also affects the way other components interact with them. In this sense we got two general types of services:
- Started Services
- Bound Services
This kind of services are launched to be run indefinitely until the task they are supposed to perform is complete, it’s the developers responsibility to stop them either calling stopSelf() from the service or stopService(Intent) from a Context once the task is complete. This type of service is initiated by calling startService(Intent) from the Context.
Services meant to be started must implement the onStartCommand(Intent) function, the system will call this function every time a startService(Intent) call is issued by any component. From this function you can get the Intent that was used to initiate the service, so you can use it to pass arguments or data to your service; onStartCommand(Intent) function also expects that you return an integer that describes how the service should handle the restart of this service (if it should be restarted at all) in the case is killed by the system. The Service class has a set of constants for the possible cases:
- START_REDELIVER_INTENT => In the case this service is killed after onStartCommand(Intent) has returned, when recreating the service send the last intent passed to startService(Intent).
- START_STICKY => In the case this service is killed after onStartCommand(Intent) has returned, when recreating the service if there is no explicit calls to startService(Intent) send a
nullIntent in the onStartCommand(Intent). This is good when your service knows how to resume its work by itself.
- START_NOT_STICKY -> In the case this service is killed after onStartCommand(Intent) has returned, do not recreate the service unless there is an explicit call to startService(Intent).
In this example we got a simple service that performs some work in its
doWork() function, it expects a URL that is passed adding it as an extra in the Intent. Notice how the startId passed to onStartCommand is used when calling stopSelf(), in the case of several calls to startService() are issued, the service is destroyed until the work for the last call has finished.
Background and Foreground
At the same time, we can divide started services based on whether or not the user is aware of the task they perform. For instance, take a music application; you probably would want to use a service so the music continues after the application is closed, but at any time the user must know where the music is coming from, maybe even with the ability to stop it without needing to go back to the app.
From the implementation perspective, there is only one thing you need to do to ‘transform’ your background to a foreground service: you must issue a call to startForeground(Int, Notification). This makes the system to give the service a high priority removing it as a candidate when selecting components to destroy in scenarios of low memory.
Technically there is still a chance for the service to be killed on scenarios of extreme memory pressure by the foreground application, but in practice, it should not be a concern.
This kind of services on the other hand are supposed to respond to requests made by components bound to them, much like a client making a request to a remote API server and waiting for it to respond with the result, working in this Client-Server fashion makes sense that once there are no more clients to serve, the service is destroyed so no precious resources are wasted keeping the service alive for no reason. They are meant to be initiated using the bindService(Intent, ServiceConnection, Int): Boolean function in the Context.
The Service class is an abstract class which at a bare minimum requires one method to be overridden and that is
onBind(Intent): IBinder?. IBinder is an interface that describes an object that can be called remotely; it is the way Android provides to abstract the service to other components potentially in other processes, from the IBinder docs:
IBinder is the base interface for a remotable object, the core part of a lightweight remote procedure call mechanism designed for high performance when performing in-process and cross-process calls.
There are several ways you can implement the
onBind(Intent): IBinder? method and mostly depends on whether this service will be bound from other processes and if it will support multithreading.
No IPC and No Multithreading
This is the simplest scenario for a bound service, in this case, your service is meant to be used only by components in the same process and at any time the requests it dispatches come from a single thread.
In this example, we have a simple service with an inner class extending Binder that exposes the service instance through the
getService() function. Back in the activity we have to get a reference to the service instead of just starting it as we did with the
Started Services, we do this with the bindService(Intent, ServiceConnection, Int) call which requires the explicit Intent for the Service and a ServiceConnection instance, which is a class that serves as callback, it got the function
onServiceDisconnected that triggered when you successfully bind and unbind to the service respectively; it's in this callback that we are passed the Binder we originally returned in the
onBind() function, since everything is running in the same process, we just need to cast it to our
MyServiceBinder class to access the service instance and call its functions.
IPC and No Multithreading
There could be cases where you want your bound service to be available for components in your app that run on a different process or a completely independent application altogether if multithreading is still not a requirement you can implement the service using a Messenger.
It’s possible for an application to contain components that run on different components using the component’s
android:processproperty in the AndroidManifest, you can check more about this in this link.
We can see that this implementation is a little bit more complex than the previous one, in here we use a Messenger in order to communicate with the Service rather than calling its functions directly; it provides a generic way to pass arguments and objects from the activity so they can be used in the service. Again, this approach works well when multithreading is NOT a requirement; under the hood, the Messenger will collect and dispatch all the messages sent to it in a single thread.
Back in the Activity, we need to reconstruct the Messenger using the binder that we get in the onServiceConnected callback.
Any other scenario
When none of the previous methods is enough to fit your needs you can use Android Interface Definition Language (AIDL). It is pretty similar to other IDL, and it allows you to define an interface that the client and the service can use to communicate with each other using primitives. The Messenger method described above uses AIDL under the hood. You use Java syntax to define the AIDL interface, and the Android plugin will generate a
.java file based on the it that you can use in your service to define your binder.
Showing all the ins and outs of AIDL is beyond the scope of this post, but you can visit AIDL for more information about it.
It’s important to note that Bound and Started services are not mutually exclusive; a bound service can also be a started service. It only depends if you implement the onBind and onStartCommand functions together. Going back to the Music Player app sample makes sense that the service playing the music is also a bound service so the activity can bind to it and provide the user with buttons to control the playback, and once the user exits it continues playing music.
This is just a subclass of the Service, it’s perfect for cases where the service needs to perform the work in a background thread. IntentService handles the intents sent to it in its onHandleIntent(Intent) and this is done in a worker thread, once the work is done IntentService handles stopping itself, so you get a lot of the hard stuff handled for you at the cost of some flexibility.
So, this is it. Hopefully, it should be clear now how Services work. They are a core part of the Android Framework, as Android developer sooner rather than later you will run into them and it’s better to be prepared.