Understanding Android Started and Bound Services

In Android, a Service is an application component that can perform long-running operations in the background on the UI thread. By background, it means that it doesn’t have a user interface. A Service runs on the main thread of the calling Component’s process by default (and hence can degrade responsiveness and cause ANRs), hence you should create a new Thread to perform long running operations. A Service can also be made to run in a completely different process.

Unlike Activity components, Services do not have any graphical interfaces. Also Broadcast Receivers are for receiving broadcast messages (broadcast, multicast, unicast) and perform short tasks whereas Services are meant to do lengthy processing like streaming music, network transactions, file I/O, interact with databases, etc. When a Service is started by an application component like an Activity it runs in the background and keeps running even if the user switches to another application or the starting component is itself destroyed.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Services are given higher priority than other Background processes and hence it’s less likely that Android will terminate it. Although it can be configured to restart once there is ample resources available again. You should go through the different processes and their priority/important level in the documentation on processes and threads. Assigning them the same priority as foreground activities is definitely possible in which case it’ll need to have a visible notification active (generally used for Services playing music).

Services for Asynchronous Execution

There are a couple of benefits of using Services (with Threads ofcourse) for background operations over using regular threads in say an Activity or a Broadcast Receiver.

  • When a process is terminated by the runtime, that’ll terminate all the background threads which won’t restart by default whereas a Service maybe restarted on termination by the system. Also the runtime terminates processes based on their importance where a Service process has more importance than the regular threads.
  • Activity and BroadcastReceiver are decoupled from the threads that they spawn whereas a Service can couple with the Threads it creates and end its lifecycle when the background task is done using stopSelf() for instance.

Local and Remote Service

Components start a Service through intents. These components are also referred to as client components sometimes. Now this invocation can happen in three ways:

  • Local Service – Both the component and Service of the application runs in the same process.
  • Private Remote Service – The Service runs in a different process from the component but only accessible to components that belongs to only that particular application.
  • Global Remote Service – Pretty much similar to Private Remote Services but also accessible to other applications, not through the class name (as they won’t know it) but through Intent Filters.

For remote services, the separate process has its own main thread which doesn’t delay the execution of the UI threads of the client components.

Services can be of two types – Started and Bound.

Started Service

Started services are those that are launched by other application components like an Activity or a Broadcast Receiver. They can run indefinitely in the background until stopped or destroyed by the system to free up resources.

Let’s see how to create a Service (very basic) now:

public class TestService extends Service {

    private String TAG = "TestService";

    public TestService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate called");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand executed");
        return Service.START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }

}

You’ll also need to create an entry in the manifest file inside the application tags:

<service
  android:name="com.example.app.TestService">
</service>

Now that we’re set with our Service, it’s time to see how to trigger its execution. Here’s how a Service (from let’s say an Activity or a Broadcast Receiver component) is started:

// Create an Explicit Intent
Intent intent = new Intent(this, TestService.class);
// Set some data that the Service might require/use
intent.putExtra("key", "val");
// Start the Service
startService(intent);

If the Service is not yet running, then startService() triggers the onCreate() method of the Service first. Once the Service is started (or it is already running), then the onStartCommand() is called with the Intent object passed to startService(). So basically a Service is started only once no matter how often you call startService() (delivered/processed sequentially). Calls to startService() are non-blocking even if some processing has to be done in the Service class.

Let’s examine the 3 different arguments passed to onStartCommand():

  • Intent – Data to be used by the Service for any sort of asynchronous execution. For example you can pass an image URL that must be downloaded.
  • Flag – Representing the history of this start request. We will discuss this in a bit.
  • Start ID – A unique ID provided by the runtime for this start request. If the process is terminated and then at a later stage restarted then onStartCommand() will be called with the same start ID. It can be used with stopSelfResult() which is similar to stopService() but this way you can avoid stopping the Service for old start requests that have not been handled by onStartCommand() yet because maybe during that start request the Service terminated and restarted.

If you want to stop the Service, then just call stopService() once:

// Create the same Explicit Intent
Intent intent = new Intent(this, TestService.class);
// Stop the Service
stopService(intent);

The Service can also stop itself by calling stopSelf() when it finishes its work.

Services must implement the onBind() method. Since Started Services do not support binding, we can have a minor implementation of returning null.

So the Service might be terminated by the runtime incase there are too many processes running on the system or by a stopService() call. The second argument passed to onStartCommand() and the return value of onStartCommand() states what should happen after the Service is terminated, i.e., the restart behaviour. Let’s examine the different values:

  • START_STICKY – Service will be restarted if it gets terminated whether any requests are pending or not. The Intent of the earlier request passed (before termination) will not be resubmitted, instead null will be passed as Intent data. Hence use this option for Services that do not depend on Intent data to manage its state. All the pending requests are delivered with the START_FLAG_RETRY as the second argument (can also get using Intent.getFlags()).
  • START_NOT_STICKY – Service is only restarted for pending requests. The Intent data specified while making the startService() calls will be passed.
  • START_REDELIVER_INTENT – Similar to START_STICKY and will receive both pending and started requests. The pending requests are delivered with the START_FLAG_RETRY flag set in the second argument whereas previously started requests are redelivered with the START_FLAG_REDELIVERY flag set. Also the original Intent is re-delivered.

Pending service requests are the startService() calls since the service termination. If a Service is terminated while the onStartCommand() method is running, it is considered to be a pending request. Start requests are when onStartCommand() has been entirely executed and returned from.

When a new request is submitted, i.e., the Service is started with startService(), the second flag argument passed is the value 0.

Bound Service

Started services cannot return results/values or interact with its starting component. Bound services on the other hand can send data to the launching component (client). So for example a bound service might be playing an audio file and sending data regarding audio start/pause/stop and the time elapsed to the launching Activity component so that the UI can be updated accordingly.

So the client component starts the bound Service and binds to it. Multiple components can bind to the service simultaneously. Obviously when the client is done, it can unbind. When the last bound client unbinds, the runtime will terminate the service. Internally, the Service keeps a reference counter holding the number of bound components which on decrementing to 0 triggers the destruction of the Service.

Binding is done using the Context.bindService() method whereas it is terminated with Context.unbindService(). The binding is also destroyed if the client component’s lifecycle ends.

When a client component binds to a Bound Service it retrieves a communication interface for sending requests and receiving responses within the process or even across processes. It returns an IBinder interface implementation from its `onBind()` method through the ServiceConnection interface supplied by the binding client component when invoking bindService(). This IBinder is used as the communication channel by the bound component. The component invokes methods of the communication interface and the execution happens in the Service.

Let’s go through some code now. Here’s a sample Service to which our component will bind to:

public class TestService extends Service {

    private String TAG = "TestService";

    private IBinder myBinder = new MyBinder();

    public TestService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate called");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind done");
        return myBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

    public class MyBinder extends Binder {
        TestService getService() {
            return TestService.this;
        }
    }

    
    // Methods used by the binding client components
    
    public void methodOne() {
        // Some code...
    }
    
    public void methodTwo() {
        // Some code...
    }

}

The onBind() method is called when the client component binds to the Service for the first time through bindService(). It returns an IBinder implementation that the client can use to interact with the Service by calling methods in the Service class. This IBinder implementation is basically a communication interface to send requests and receive responses either within a particular process or across processes. On the other hand, onUnbind() is called when all bindings are unbound.

The IBinder is returned to the client through a ServiceConnection interface that the client has to supply while binding. Let’s see how the binding is done from the client component:

Intent intent = new Intent(this, TestService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

So the Intent defines the Service to bind to and shouldn’t contain any other extra data. The binding client has to provide a ServiceConnection implementation which would look like this:

TestService testService;
boolean isBound = false;

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        TestService.MyBinder binder = (TestService.MyBinder) service;
        testService = binder.getService();
        isBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        testService = null;
        isBound = false;
    }
};

This way the binding client gets notify when the binding is established or disconnected. This way the binding connection can be observed. The last argument (flags) passed to bindService() defines the operation options. These options can either determine the restart strategy on destruction of the Service or the rank of the Service’s process. For instance BIND_AUTO_CREATE is used commonly which recreates the Service if it is destroyed when there’s a bounding client.

Now that you’ve the testService object, you can call important methods like methodOne() and methodTwo() for the sake of communication and receive responses. Note this execution happens on the thread of the calling client. Hence if it’s the UI thread, long running operations should be prevented and a new Thread should be spawned to handle them in background in methodOne() and methodTwo().

Although multiple clients can bind to the service, the system calls your Service’s onBind() only once when the first client binds to retrieve the IBinder. The system then won’t call onBind() again when any additional clients bind. Instead it’ll deliver the same IBinder.

The example that we just covered is actually Local Binding. Local binding to a Service in the same application process is the most common type of binding. Both of them run in the same process, hence the same VM and shares the heap memory. Also there are no IPC (Inter Process Communication) complexities involved. In another article we’ll cover remote binding which’ll involve communication across processes.

Running a Service in the Foreground

A Foreground Service is one where the user is actively aware of it putting it on high priority hence won’t be killed when the system is low on memory. It must provide a notification for the status bar which cannot be dismissed unless the Service is stopped or removed from the foreground. A good example of such a notification is a music player that shows the current song and other action buttons like play/pause, next, previous, etc. in a notification in the status bar. VOIP calls or file download apps could also start a foreground Service and show similar notifications.

To request your Service to run in the Foreground, you’ll need to use the startForeground() method. You’ll see a sample code in the documentation that you can simply copy paste in your onStartCommand() for instance and use.

Conclusion

So we learnt how Android Services can be invoked and made to run forever or bind to and eventually use to perform long running tasks. We covered the two types of Services – Started and Bound. In a later article we’ll see how to invoke a Remote Service across process boundaries.

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

7 thoughts on “Understanding Android Started and Bound Services”

  1. Great article!

    I am trying to figure out what the best choice is in the following scenario: The app needs a location service that runs in the background even when the app is not active, and does not risk being shut down due to low system resources. I assume that would be a Foreground Service, am I right? What about communication with the activity/app that started the foreground service? I cannot find the right option when it comes to type of bound service. Or is another method preferred?

  2. So the difference between a bound service and a generic service is that the bound service responds to some input from the activity and a generic service goes on continuously as a thread?

  3. hii,
    about “start_not_sticky”, if before the service termination there was start service commands that not performed(i.e “onStartCommand” not executed with their intent that delivered yet), so when the service will be restart, that start service commands will be executed with their original intent(i.e “onStartCommand” will be called with their original intent), (regardless explicit call to start service after the the service restarted), is it true?

Leave a Reply

Your email address will not be published. Required fields are marked *

*