An Overview of Android Started and Bound Services
Previous | Table of Contents | Next |
A Basic Overview of Threads and AsyncTasks | Implementing an Android Started Service – A Worked Example |
The Android Service class is designed specifically 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.
In this chapter, an overview of the different types of services available will be covered, including started services, bound services and intent services. Once these basics have been covered, subsequent chapters will work through a number of examples of services in action.
Started Services
Started services are launched by other application components (such as an activity or even a broadcast receiver) and potentially run indefinitely in the background until the service is stopped, or is destroyed by the Android runtime system in order to free up resources. A service will continue to run if the application that started it is no longer in the foreground, and even in the event that the component that originally started the service is destroyed.
By default, a service will run within the same main thread as the application process from which it was launched (referred to as a local service). It is important, therefore, that any CPU intensive tasks be performed in a new thread within the service. Instructing a service to run within a separate process (and therefore known as a remote service) requires a configuration change within the manifest file.
Unless a service is specifically configured to be private (once again via a setting in the manifest file), that service can be started by other components on the same Android device. This is achieved using the Intent mechanism in the same way that one activity can launch another, as outlined in preceding chapters.
Started services are launched via a call to the startService() method, passing through as an argument an Intent object identifying the service to be started. When a started service has completed its tasks, it should stop itself via a call to stopSelf(). Alternatively, a running service may be stopped by another component via a call to the stopService() method, passing through as an argument the matching Intent for the service to be stopped.
Services are given a high priority by the Android system and are typically among the last to be terminated in order to free up resources.
Intent Service
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 take place within a new thread, thereby avoiding impacting the performance of the calling application.
The IntentService class is a convenience class (subclassed from the Service class) that sets up a worker thread for handling background tasks and handles each request in an asynchronous manner. Once the service has handled all queued requests, it simply exits. All that is required when using the IntentService class is that the onHandleIntent() method be implemented containing the code to be executed for each request.
For services that do not require synchronous processing of requests, IntentService is the recommended option. Services requiring synchronous handling of requests will, however, need to subclass from the Service class and manually implement and manage threading to handle any CPU intensive tasks efficiently.
Bound Service
A bound service is similar to a started service with the exception that a started service does not generally return results or permit interaction with the component that launched it. A bound service, on the other hand, allows the launching component to interact with, and receive results from, the service. Through the implementation of interprocess communication (IPC), this interaction can also take place across process boundaries. 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 for the purpose of pausing playback or skipping to the next track. Similarly, the service will quite likely need to communicate information to the calling activity to indicate that the current audio track has completed and to provide details of the next track that is about to start playing.
A component (also 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 the service binding is no longer required by a client, a call should be made to the unbindService() method. When the last bound client unbinds from a service, the service will be terminated by the Android runtime system. It is important to keep in mind 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.
In terms of 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
A service must, as has already been mentioned, 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 is the method that is called when the service is started by another component 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. In the case of started services, this method must be implemented to return a NULL value.
- 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.
- onHandleIntent() – Applies only to IntentService 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 in the event that it is destroyed by the Android runtime system. Possible return values for these methods are as follows:
- START_NOT_STICKY – Indicates to the system that the service should not be restarted in the event that 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. In the event that no pending intents are waiting to be delivered, the onStartCommand() callback method is called with a NULL intent value. The intent being processed at the time that 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
In order for a service to be useable, 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=".TestActivity" > <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>
By default, services are declared as public, in that they can be accessed by components outside of the application package in which they reside. In order 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>
As previously discussed, services run within the same process as the calling component by default. In order 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>
The colon prefix indicates that the new process is private to the local application. If the process name begins with a lower case 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 entitled “An Overview of Android Intents”.
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 simply invoke the necessary service and then return. Note that, in order to function, such a broadcast receiver will need to request the android.permission.RECEIVE_BOOT_COMPLETED permission.
Summary
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 in the event that the component that initiated the service is destroyed.
Services are subclassed from the Android Service class and fall into the category of either started services or bound services. Started services run until they are stopped or destroyed and do not inherently provide a mechanism for interaction or data exchange with other components. Bound services, on the other hand, 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 for the purpose of handling 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 (itself a subclass of the Android Service class) provides a convenient mechanism for handling asynchronous service requests within a separate worker thread.
Previous | Table of Contents | Next |
A Basic Overview of Threads and AsyncTasks | Implementing an Android Started Service – A Worked Example |