The Android Service class is designed to allow applications to initiate and perform background tasks. Unlike broadcast receivers, which are intended to perform a task quickly and then exit, services are designed to perform tasks that take a long time to complete (such as downloading a file over an internet connection or streaming music to the user) but do not require a user interface.
This chapter will provide an overview of the services available, including bound and intent services. Once these basics have been covered, subsequent chapters will work through some examples of services in action.
As previously outlined, services run by default within the same main thread as the component from which they are launched. As such, any CPU-intensive tasks that need to be performed by the service should occur within a new thread, thereby avoiding impacting the performance of the calling application.
The JobIntentService class is a convenience class (subclassed from the Service class) that sets up a worker thread for handling background tasks and handles each request asynchronously. Once the service has handled all queued requests, it exits. All that is required when using the JobIntentService class is to implement the onHandleWork() method, containing the code to be executed for each request.
For services that do not require synchronous processing of requests, JobIntentService is the recommended option. However, services requiring synchronous handling of requests will need to subclass from the Service class and manually implement and manage threading to handle any CPU-intensive tasks efficiently.
A bound service allows a launching component to interact with and receive results from the service. This interaction can also occur across process boundaries through the implementation of interprocess communication (IPC). An activity might, for example, start a service to handle audio playback. The activity will, in all probability, include a user interface providing controls to the user to pause playback or skip to the next track. Similarly, the service will likely need to communicate information to the calling activity to indicate that the current audio track has ended and provide details of the next track that is about to start playing.
A component (referred to in this context as a client) starts and binds to a bound service via a call to the bindService() method. Also, multiple components may bind to a service simultaneously. When a client no longer requires the service binding, a call should be made to the unbindService() method. When the last bound client unbinds from a service, the Android runtime system will terminate the service. It is important to remember that a bound service may also be started via a call to startService(). Once started, components may then bind to it via bindService() calls. When a bound service is launched via a call to startService(), it will continue to run even after the last client unbinds from it.
A bound service must include an implementation of the onBind() method, which is called both when the service is initially created and when other clients subsequently bind to the running service. The purpose of this method is to return to binding clients an object of type IBinder containing the information needed by the client to communicate with the service.
When implementing the communication between a client and a bound service, the recommended technique depends on whether the client and service reside in the same or different processes and whether or not the service is private to the client. Local communication can be achieved by extending the Binder class and returning an instance from the onBind() method. Interprocess communication, on the other hand, requires Messenger and Handler implementation. Details of both of these approaches will be covered in later chapters.
The Anatomy of a Service
As has already been mentioned, a service must be created as a subclass of the Android Service class (more specifically, android.app.Service) or a sub-class thereof (such as android.app.IntentService). As part of the subclassing procedure, one or more of the following superclass callback methods must be overridden, depending on the exact nature of the service being created:
- onStartCommand() – This method is called when another component starts the service via a call to the startService() method. This method does not need to be implemented for bound services.
- onBind() – Called when a component binds to the service via a call to the bindService() method. When implementing a bound service, this method must return an IBinder object facilitating communication with the client.
- onCreate() – Intended as a location to perform initialization tasks, this method is called immediately before the call to either onStartCommand() or the first call to the onBind() method.
- onDestroy() – Called when the service is being destroyed.
- onHandleWork() – Applies only to JobIntentService subclasses. This method is called to handle the processing for the service. It is executed in a separate thread from the main application.
Note that the IntentService class includes its own implementations of the onStartCommand() and onBind() callback methods, so these do not need to be implemented in subclasses.
Controlling Destroyed Service Restart Options
The onStartCommand() callback method is required to return an integer value to define what should happen with regard to the service if the Android runtime system destroys it. Possible return values for these methods are as follows:
- START_NOT_STICKY – Indicates to the system that the service should not be restarted if it is destroyed unless there are pending intents awaiting delivery.
- START_STICKY – Indicates that the service should be restarted as soon as possible after it has been destroyed if the destruction occurred after the onStartCommand() method returned. If no pending intents are waiting to be delivered, the onStartCommand() callback method is called with a NULL intent value. The intent being processed when the service was destroyed is discarded.
- START_REDELIVER_INTENT – Indicates that if the service was destroyed after returning from the onStartCommand() callback method, the service should be restarted with the current intent redelivered to the onStartCommand() method followed by any pending intents.
Declaring a Service in the Manifest File
For a service to be usable, it must first be declared within a manifest file. This involves embedding an appropriately configured <service> element into an existing <application> entry. At a minimum, the <service> element must contain a property declaring the class name of the service, as illustrated in the following XML fragment:
. . <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".MainActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService> </service> </application> </manifest>Code language: HTML, XML (xml)
By default, services are declared public in that they can be accessed by components outside the application package in which they reside. To make a service private, the android:exported property must be declared as false within the <service> element of the manifest file. For example:
<service android:name="MyService" android:exported="false"> </service>Code language: HTML, XML (xml)
When working with JobIntentService, the manifest Service declaration must also request the BIND_JOB_ SERVICE permission as follows:
<service android:name=".MyJobIntentService" android:permission="android.permission.BIND_JOB_SERVICE" />Code language: HTML, XML (xml)
As previously discussed, services run within the same process as the calling component by default. To force a service to run within its own process, add an android:process property to the <service> element, declaring a name for the process prefixed with a colon (:):
<service android:name=".MyService" android:exported="false" android:process=":myprocess"> </service>Code language: HTML, XML (xml)
The colon prefix indicates that the new process is private to the local application. If the process name begins with a lowercase letter instead of a colon, however, the process will be global and available for use by other components.
Finally, using the same intent filter mechanisms outlined for activities, a service may also advertise capabilities to other applications running on the device. For more details on intent filters, refer to the chapter An Android Intents Overview.
Starting a Service Running on System Startup
Given the background nature of services, it is not uncommon for a service to need to be started when an Android-based system first boots up. This can be achieved by creating a broadcast receiver with an intent filter configured to listen for the system android.intent.action.BOOT_COMPLETED intent. When such an intent is detected, the broadcast receiver would invoke the necessary service and then return. Note that, to function, such a broadcast receiver must request the android.permission.RECEIVE_BOOT_COMPLETED permission.
Android services are a powerful mechanism that allows applications to perform tasks in the background. A service, once launched, will continue to run regardless of whether the calling application is the foreground task or not and even if the component that initiated the service is destroyed.
Services are subclassed from the Android Service class. Bound services provide a communication interface to other client components and generally run until the last client unbinds from the service.
By default, services run locally within the same process and main thread as the calling application. A new thread should, therefore, be created within the service to handle CPU-intensive tasks. Remote services may be started within a separate process by making a minor configuration change to the corresponding <service> entry in the application manifest file.
The IntentService class (a subclass of the Android Service class) provides a convenient mechanism for handling asynchronous service requests within a separate worker thread.