Advertisement
  1. Code
  2. Coding Fundamentals

Concurrency on Android with Service

Scroll to top

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.

1
public class CustomService extends Service {
2
    @Override
3
    public int onStartCommand(Intent intent, int flags, int startId) {
4
        // Execute your operations

5
        // Service wont be terminated automatically

6
        return Service.START_NOT_STICKY;
7
    }
8
9
    @Nullable
10
    @Override
11
    public IBinder onBind(Intent intent) {
12
        // Creates a connection with a client

13
        // using a interface implemented on IBinder

14
        return null;
15
    }
16
}
  • 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.

1
<manifest ... >
2
  ...
3
  <application ... >
4
      <service 
5
        android:name=".ExampleService"
6
        android:process=":my_process"/>
7
      ...
8
  </application>
9
</manifest>

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.

1
Intent serviceIntent = new Intent(this, CustomService.class);
2
// Pass data to be processed on the Service

3
Bundle data = new Bundle();
4
data.putInt("OperationType", 99);
5
data.putString("DownloadURL", "https://mydownloadurl.com");
6
serviceIntent.putExtras(data);
7
// Starting the Service

8
startService(serviceIntent);

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.

1
    @Override
2
    public int onStartCommand(Intent intent, int flags, int startId) {
3
4
        Bundle data = intent.getExtras();
5
        if (data != null) {
6
            int operation = data.getInt(KEY_OPERATION);
7
            // Check what operation to perform and send a msg

8
            if ( operation == OP_DOWNLOAD){
9
                // make a download

10
            }
11
        }
12
13
        return START_STICKY;
14
    }

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.

1
void someOperation() {
2
        // do some long-running operation

3
        // and stop the service when it is done

4
        stopSelf();
5
    }

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.

1
void bindWithService() {
2
        Intent intent = new Intent(this, PlayerService.class);
3
        // bind with Service

4
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
5
    }

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.

1
private ServiceConnection mConnection = new ServiceConnection() {
2
        @Override
3
        public void onServiceConnected(ComponentName className,
4
                                       IBinder service) {
5
            // use the IBinder received to create a Messenger

6
            mServiceMessenger = new Messenger(service);
7
            mBound = true;
8
        }
9
10
        @Override
11
        public void onServiceDisconnected(ComponentName arg0) {
12
            mBound = false;
13
            mServiceMessenger = null;
14
        }
15
    };

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.

1
    private Handler mResponseHandler = new Handler() {
2
        @Override
3
        public void handleMessage(Message msg) {
4
            // handle response from Service

5
        }
6
    };
7
    Message msgReply = Message.obtain();
8
    msgReply.replyTo = new Messenger(mResponseHandler);
9
    try {
10
        mServiceMessenger.send(msgReply);
11
    } catch (RemoteException e) {
12
        e.printStackTrace();
13
    }

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

1
@Override
2
    protected void onDestroy() {
3
        super.onDestroy();
4
        // disconnect from service

5
        if (mBound) {
6
            unbindService(mConnection);
7
            mBound = false;
8
        }
9
    }

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.

1
    IncomingHandler(PlayerService playerService) {
2
            mPlayerService = new WeakReference<>(playerService);
3
        }
4
5
        @Override
6
        public void handleMessage(Message msg) {
7
            // handle messages

8
        }
9
    }
10
    
11
    public IBinder onBind(Intent intent) {
12
        // pass a Binder using the Messenger created

13
        return mMessenger.getBinder();
14
    }
15
    
16
    final Messenger mMessenger = new Messenger(new IncomingHandler(this));

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.

1
    private Looper mServiceLooper;
2
    private ServiceHandler mServiceHandler;
3
    // Handler to receive messages from client

4
    private final class ServiceHandler extends Handler {
5
        ServiceHandler(Looper looper) {
6
            super(looper);
7
        }
8
9
        @Override
10
        public void handleMessage(Message msg) {
11
            super.handleMessage(msg);
12
            // handle messages

13
14
            // stopping Service using startId

15
            stopSelf( msg.arg1 );
16
        }
17
    }
18
    
19
    @Override
20
    public void onCreate() {
21
        HandlerThread thread = new HandlerThread("ServiceThread",
22
                Process.THREAD_PRIORITY_BACKGROUND);
23
        thread.start();
24
25
        mServiceLooper = thread.getLooper();
26
        mServiceHandler = new ServiceHandler(mServiceLooper);
27
28
    } 

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.

1
public class MyIntentService extends IntentService {
2
    
3
    public MyIntentService() {
4
        super("MyIntentService");
5
    }
6
7
    @Override
8
    protected void onHandleIntent(Intent intent) {
9
        // handle Intents send by startService

10
    }
11
}

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.

1
    private Handler mResponseHandler = new Handler() {
2
        @Override
3
        public void handleMessage(Message msg) {
4
            // handle response from Service

5
        }
6
    };
7
8
    private ServiceConnection mConnection = new ServiceConnection() {
9
        @Override
10
        public void onServiceConnected(ComponentName className,
11
                                       IBinder service) {
12
            // use the IBinder received to create a Messenger

13
            mServiceMessenger = new Messenger(service);
14
15
            Message msgReply = Message.obtain();
16
            msgReply.replyTo = new Messenger(mResponseHandler);
17
            try {
18
                mServiceMessenger.send(msgReply);
19
            } catch (RemoteException e) {
20
                e.printStackTrace();
21
            }
22
        }

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!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.