Concurrency on Android with Service

In this tutorial we’ll explore the Service component and its superclass, the IntentService. You'll learn when and how to use this component to create great concurrency solutions for long-running background operations. We’ll also take quick look at IPC (Inter Process Communication), to learn how to communicate with services running on different processes.

To follow this tutorial you'll need some understanding of concurrency on Android. If you don’t know much about it, you might want to read some of our other articles about the topic first.

1. The Service Component

The Service component is a very important part of Android's concurrency framework. It fulfills the need to perform a long-running operation within an application, or it supplies some functionality for other applications. In this tutorial we’ll concentrate exclusively on Service’s long-running task capability, and how to use this power to improve concurrency.

What is a Service?

Service is a simple component that's instantiated by the system to do some long-running work that doesn't necessarily depend on user interaction. It can be independent from the activity life cycle and can also run on a complete different process.

Before diving into a discussion of what a Service represents, it's important to stress that even though services are commonly used for long-running background operations and to execute tasks on different processes, a Service doesn't represent a Thread or a process. It will only run in a background thread or on a different process if it's explicitly asked to do so.

A Service has two main features:

  • A facility for the application to tell the system about something it wants to be doing in the background.
  • A facility for an application to expose some of its functionality to other applications.

Services and Threads

There is a lot of confusion about services and threads. When a Service is declared, it doesn't contain a Thread. As a matter of fact, by default it runs directly on the main thread and any work done on it may potentially freeze an application. (Unless it's a IntentService, a Service subclass that already comes with a worker thread configured.)

So, how do services offer a concurrency solution? Well, a Service doesn't contain a thread by default, but it can be easily configured to work with its own thread or with a pool of threads. We'll see more about that below.

Disregarding the lack of a built-in thread, a Service is an excellent solution for concurrency problems in certain situations. The main reasons to choose a Service over other concurrency solutions like AsyncTask or the HaMeR framework are:

  • A Service can be independent of activity life cycles.
  • A Service is appropriate for running long operations.
  • Services don't depend on user interaction.
  • When running on different processes, Android can try to keep services alive even when the system is short on resources.
  • A Service can be restarted to resume its work.

Service Types

There are two types of Service, started and bound.

started service is launched via Context.startService(). Generally it performs only one operation and it will run indefinitely until the operation ends, then it shuts itself down. Typically, it doesn't return any result to the user interface.

The bound service is launched via Context.bindService(), and it allows a two-way communication between client and Service. It can also connect with multiple clients. It destroys itself when there isn't any client connected to it.

To choose between those two types, the  Service must implement some callbacks: onStartCommand() to run as a started service, and onBind() to run as a bound service. A Service may choose to implement only one of those types, but it can also adopt both at the same time without any problems. 

2. Service Implementation

To use a service, extend the Service class and override its callback methods, according to the type of Service . As mentioned before, for started services the onStartCommand() method must be implemented and for bound services, the onBind() method. Actually, the onBind() method must be declared for either service type, but it can return null for started services.

  • onStartCommand(): launched by Context.startService(). This is usually called from an activity. Once called, the service may run indefinitely and it's up to you to stop it, either calling stopSelf() or stopService().
  • onBind(): called when a component wants to connect to the service. Called on the system by Context.bindService(). It returns an IBinder that provides an interface to communicate with the client.

The service's life cycle is also important to take into consideration. The onCreate()  and onDestroy() methods should be implemented to initialize and shut down any resources or operations of the service.

Declaring a Service on Manifest

The Service component must be declared on the manifest with the <service> element. In this declaration it's also possible, but not obligatory, to set a different process for the Service to run in.

2.2. Working with Started Services

To initiate a started service you must call Context.startService() method. The Intent must be created with the Context and the Service class. Any relevant information or data should also be passed in this Intent.

In your Service class, the method that you should be concerned about is the onStartCommand(). It's on this method that you should call any operation that you want to execute on the started service. You'll process the Intent to capture information sent by the client. The startId represents an unique ID, automatically created for this specific request and the flags can also contain extra information about it.

The onStartCommand() returns a constant int that controls the behavior:

  • Service.START_STICKY: Service is restarted if it gets terminated.
  • Service.START_NOT_STICKY: Service is not restarted.
  • Service.START_REDELIVER_INTENT: The service is restarted after a crash and the intents then processing will be redelivered.

As mentioned before, a started service needs to be stopped, otherwise it will run indefinitely. This can be done either by the Service calling stopSelf() on itself or by a client calling stopService() on it.

Binding to Services

Components can create connections with services, establishing a two-way communication with them. The client must call Context.bindService(), passing an Intent, a ServiceConnection interface and a flag as parameters. A Service can be bound to multiple clients and it will be destroyed once it has no clients connected to it.

It's possible to send Message objects to services. To do it you'll need to create a Messenger on the client side in a ServiceConnection.onServiceConnected interface implementation and use it to send Message objects to the Service.

It's also possible to pass a response Messenger to the Service for the client to receive messages. Watch out though, because the client may not no longer be around to receive the service's message. You could also use BroadcastReceiver or any other broadcast solution.

It's important to unbind from the Service when the client is being destroyed.

On the Service side, you must implement the Service.onBind() method, providing an IBinder provided from a Messenger. This will relay a response Handler to handle the Message objects received from client.

3 Concurrency Using Services

Finally, it's time to talk about how to solve concurrency problems using services. As mentioned before, a standard Service doesn't contain any extra threads and it will run on the main Thread by default. To overcome this problem you must add an worker Thread, a pool of threads or execute the Service on a different process. You could also use a subclass of Service called IntentService that already contains a Thread.

Making a Service Run on a Worker Thread

To make the Service execute on a background Thread you could just create an extra Thread and run the job there. However Android offers us a better solution. One way to take the best advantage of the system is to implement the HaMeR framework inside the Service, for example by looping a Thread with a message queue that can process messages indefinitely.

It's important to understand that this implementation will process tasks sequentially. If you need to receive and process multiple tasks at the same time, you should use a pool of threads. Using thread pools is out of the scope of this tutorial and we won't talk about it today. 

To use HaMeR you must provide the Service with a Looper, a Handler and a HandlerThread.

If the HaMeR framework is unfamiliar to you, read our tutorials on HaMer for Android concurrency.

The IntentService

If there is no need for the Service to be kept alive for a long time, you could use IntentService, a Service subclass that's ready to run tasks on background threads. Internally, IntentService is a Service with a very similar implementation to the one proposed above. 

To use this class, all you have to do is extend it and implement the onHandleIntent(), a hook method that will be called every time a client calls startService() on this Service. It's important to keep in mind that the IntentService will stop as soon as its job is completed.

IPC (Inter Process Communication)

A Service can run on a completely different Process, independently from all tasks that are happening on the main process. A process has its own memory allocation, thread group, and processing priorities. This approach can be really useful when you need to work independently from the main process.

Communication between different processes is called IPC (Inter Process Communication). In a Service there are two main ways to do IPC: using a Messenger or implementing an AIDL interface. 

We've learned how to send and receive messages between services. All that you have to do is use create a Messenger using the IBinder instance received during the connection process and use it to send a reply Messenger back to the Service.

The AIDL interface is a very powerful solution that allows direct calls on Service methods running on different processes and it's appropriate to use when your Service is really complex. However, AIDL is complicated to implement and it's rarely used, so its use won't be discussed in this tutorial.

4. Conclusion

Services can be simple or complex. It depends on the needs of your application. I tried to cover as much ground as possible on this tutorial, however I've focused just on using services for concurrency purposes and there are more possibilities for this component. I you want to study more, take a look at the documentation and Android guides

See you soon!



Related Articles