Chapter    22

Leveraging Android Services

Android’s underpinnings of the Linux operating system extend to multiple areas of the platform, many of which you have explored already. As with almost every operating system in existence, Android provides for a specific class of program that runs independently of any user interface, running in the background to provide crucial functionality regardless of other activity. In Android these programs are called services, similar to the services concept in Windows, or the daemon concept in Unix and Linux.

In this chapter, I explore the rationale and fundamentals of Android services, and the steps you need to take to create, start, work with, and control them. We also explore some simple service examples to help you complete your understanding not only of how to use services, but how to create your own.

Services Background

The need for services has numerous sources, most of which stem from any situation in which one or more applications require functionality without any specific need to see or access an activity or other user interface. Dozens of services run on a stock Android device, and undoubtedly you can think of many more possibilities you can code yourself, such as these:

  • Provide a local interface to control a remote API such as a mapping service or social network.
  • Maintain long-lived connections for chat-like applications that have “conversations” running over days, weeks, months, or more.
  • Continue with processing a task or piece of work once invoked by a user, without further interaction. A great example is downloading an Android application update once the user has requested it.

There are, of course, many more examples, and you will often find that a more complex service pairs itself with some other application that can trigger its work, check its status, and so on. For instance, a home-screen audio player widget actually triggers a background service to do the main task of playing back an audio track.

There is no strict requirement mandating that you use services with your applications. Instead, you should think of them as a very useful kind of resource upon which you can call if you need to, but that you can forget about at other times. Many of the applications you write will have no need for services at all. But some of your applications will call out for the use of services, both of the stock Android variety and the ones you write yourself.

Defining Your Own Service

The process of defining and creating your own service application is very similar to the work you have already learned about when you created normal activity-based Android applications. The broad steps should look very familiar to you:

  • Using an Android-provided base class, you extend and inherit to create your own specific service.
  • Decide on the callback methods you need to override, and code the logic to implement your desired behavior.
  • Update the AndroidManifest.xml file to provide the permissions, definitions, and links into the wider Android platform you need for your service to run and serve applications.

Let’s take a look at these three areas in a little more detail.

Implementing Your Own Service Class

Android provides the basic Service class as a foundation from which you can build your own service, and it also offers several useful subclasses out of the box that deal with common service patterns. The most commonly used, and most useful, of these is the IntentService subclass. You are free to use the basic Service class or aim for one of the more-specific subclasses.

The basic code skeleton should not surprise you and is shown in Listing 22-1.

Controlling Service Lifecycle via Callbacks

Android’s Service class and its subclasses provide a range of callbacks designed to be overridden so that you can implement your own logic and service control behavior. This framework is conceptually similar to the Activity and Fragment lifecycle approach, but there are distinctly fewer states in which a service will find itself, and consequently, you need to concern yourself with only five main methods at coding time.

  • onCreate(): Almost identical to the onCreate() method for Activities, the Service’s onCreate() method is invoked when any trigger for service activity happens.
  • onStartCommand(): When the related startService() method is called by a client application, the onStartCommand() method is invoked and its logic is processed.
  • onBind(): When a client application attempts to bind to the service with a bindService() call, the onBind() method is invoked.
  • onTrimMemory(): For devices running Android version 3.0 or later, services selected for resource reclamation while the device is under memory shortage have their onTrimMemory() method called. This is the somewhat-more-graceful approach to letting services try to return memory in a controlled fashion before more drastic measures are taken.
  • onDestroy(): When performing a normal graceful shutdown, onDestroy() is invoked. Just as with normal Activities, graceful shutdown is not guaranteed, and therefore neither is a call to onDestroy().

The general approach to lifecycle management stays the same. Your service should create what it needs during its onCreate() call and clean up and dispose of any lingering resources during onDestroy().

You will notice that there are no equivalents to onPause() or onResume() for a service. Your service is either running or it’s not, and there’s no need to provide background-transition methods when a service is considered always in the background. You should ensure that you minimize any state held by the service or use preferences or other storage where appropriate. Not only can Android terminate a service at any time for resources—bypassing any call to onDestroy()—but users can also kill your services via the application management system’s setting activity. This becomes more complicated if your clients decided to bind to your service in a long-lived fashion; I explore this in the examples in a moment.

Adding Manifest Entries for Your Service

To flag that you want your application recognized as a service by Android, you need to make a few modifications to your manifest file. The main change is that you need to define a <service> element as a child of the <application> element. Listing 22-2 shows the minimal entry for a service, providing the required android:name attribute, which in this case is “Skeleton”.

You can freely define a service and activities in the same project (and therefore have them both in the manifest). This is very common for applications that are developed in conjunction with their own services. If you want to protect your service from being accessed indiscriminately, you can add permissions from the set described in Chapter 20, specifying an android:permission attribute in your <service> element.

Service Communication

Defining services is really as straightforward as it seems from the introduction in this chapter. Once you have created a service, however, controlling how client applications such as activities (and even other services) communicate with them is a little more involved. How clients communicate with a service takes one of two possible paths—they either use starting commands or binding—but the converse of services communicating with clients has a world of options and choices for you to make as a developer.

Client-to-Service Communication

When any kind of client wants to work with a service, whether that client is an activity, a fragment, or some other service, one fundamental question guides which of the two communication approaches is best suited to the task. Is this a one-time, fire-and-forget request for a service to do something for the client? In these cases, the command approach to service communication is best. If the client needs to work with the service over a series of actions and wants to maintain an ongoing interaction with the service, then the bind approach is preferable.

Invoking Commands with startService( )

To have a service perform some action for your application, whether that’s from an activity or some other context, a simple call to startService() is the easiest way to go. Just as the startActivity() method introduced earlier in the book can trigger an arbitrary activity by taking an intent and some parameters, startService() also accepts an intent as the first parameter, and a set of intent extras as the second parameter so that you can pass call-specific payload data to the service. The pseudo-code form looks like this:

startService(someSignatureIntent);

In the basic pattern for calling, startService() can use the simplest form of the Intent parameter—the class of the intent itself. startService() is an asynchronous call, meaning the main UI thread of your application continues immediately and does not block on the return from the method call.

If the service itself is not started, the Android service framework takes care of starting it for you and your application user. The intent passed as a parameter is provided to the onStartCommand() method so that you can inspect and use it as you see fit within the rest of the logic you code for the onStartCommand() method. Unlike the calling application, which is not blocked by the service invocation, the onStartCommand() method is processed on the service’s main thread, so be careful not to burden your code with too much heavy processing, external calls with indeterminate timing, or anything else that might prevent a speedy response. If you need to perform such long-running work within your service, incorporate additional threads through the java.util.concurrent package and its Executor and related abilities.

Using the startService() approach does not provide return payloads in the normal sense to the calling client application. (I cover service-to-client communication later in the chapter.) The startService() call does return a value to the service instance from a range of predefined values that are mainly designed to signal whether the call completed successfully or was killed for resource-starvation or other reasons. Among the return values your service might see are these common responses:

  • START_STICKY: Restart the service once Android has enough free memory to do so, but don’t worry about the triggering intent; pass a null intent instead.
  • START_NON_STICKY: Don’t automatically restart the service at all, even if Android resource pressure drops to a low enough level to allow it. Implicitly this means the service is not started until your application or some other application explicitly calls startService() or otherwise invokes the need for the service again.
  • START_REDELIVER_INTENT: Restart the service once Android has enough free memory, and also attempt to redeliver the original Intent object passed to the service when the original (failing) call was made.

A service that starts from a startService() invocation keeps running indefinitely, assuming no low-resource conditions cause Android to kill it and the user doesn’t deactivate the device. This is in keeping with the fire-and-forget notions of startService() in that once your client has what it wants, it doesn’t really care what happens to the service afterward.

As a developer, you might, from time to time, want to explicitly and gracefully shut down a service. For such a shut down, you have two options:

  • Call the stopService() method, which is analogous to startService(), and provide either the same intent used to start the service as a parameter or an intent of a derived class. When you do, the service stops and all resources are released, the state is destroyed, and so on. Android does not track or count the number of startService() calls to a service, and it does not care which client sends the appropriate stopService() call. Only one stopService() command is needed to end the service, regardless of how many startService()methods the service received in its lifetime.
  • Code your service logic so that the service calls stopSelf() itself, in effect terminating its own existence. You might do this as part of some logical culmination to the logic of your service, such as when a download has completed or an audio track has finished playback.

You are free to experiment with either approach to stopping services or just to leave the business of service cleanup to Android. Binding with services differs markedly in this area; I cover this next.

Binding to Services with bindService( )

Whereas a command-driven approach of startService() is useful for one-time interaction with a service, at times you will want your application to have a longer-lived interaction with a service in which it sends multiple commands and receives data in response as part of the functionality you want to provide your users.

When a client binds to a service, in effect, it sets up a duplex communication channel that allows access to the service’s published API through the service’s Binder object. This Binder is the object returned from the bindService() call from the client, and it acts as the conduit for further activity. Just as with startService(), the client can signal to Android by using the BIND_AUTO_CREATE flag that it wants the service started if it is currently stopped. Unlike with the startService() approach, once a client releases the bind with the service, the service becomes a candidate to be shut down. I cover the shut down mechanics later in the chapter.

Caution  If you attempt to bind to a service that is not already running, and you do not provide the BIND_AUTO_CREATE flag as part of the bindService() call, then the method returns false and no Binder object, which leaves you without a service to talk to. Even if you think you know the service’s state, you should practice clean exception handling and always check for bindService() failure in these cases.

An additional flag is available to help you with non-graceful shutdown situations if the Android device is under memory pressure. The BIND_ALLOW_OOM_MANAGEMENT flag indicates that you are happy with your binding being considered noncritical, and that your application can tolerate the consequences of Android killing the service to recoup memory in out-of-memory situations. You are, in effect, signaling your willingness to sacrifice your binding for the sake of other applications.

A Binder class is provided by the Android framework, and normally you subclass this to implement whatever methods you want exposed as a form of API to your service’s clients. Your imagination is pretty much the only limit here—you can have as few as one method, or as many as you deem necessary to provide your desired features.

A client’s call to bindService() is an asynchronous call that includes the intent with which to identify the service, and the already-introduced optional (but usually useful) BIND_AUTO_CREATE flag. As an asynchronous method, the client making the bindService() call does not know the state of the service and this call until it interrogates the ServiceConnection object and the resulting Binder object is returned from onBind(). This is the point at which the client can start calling the Binder methods and actually interact with the functionality of the service.

Your client code is free to retain the ServiceConnection for as long as you desire. When the work with the service is ultimately finished, the unbindService() call signals that the binding to the service can be released and the ServiceConnection object and associated resources are freed. This process also means onServiceDisconnected() is eventually called, that the Binder object is no longer in scope, and that its API-like methods should no longer be invoked. If other client applications have bound to the service, and are therefore still happily using it, your call to unbindService() does not cause the service to stop. However, the last client to unbind will implicitly trigger Android to shut down the service without explicit calls or intervention from you.

Because your binding to a service is notiionally for a long time, you need to handle other lifecycle events that might affect your application. Configuration changes, such as screen rotation, are the main events to handle, and you can accommodate these by ensuring that your call to bindService() uses the application context, rather than the literal activity, so that your context survives the destroy-and-re-create process of the activity during rotation. You should also use the onRetainNonConfigurationInstance() lifecycle method to persist any state or resources you need.

Service-to-Client Communication

From the introduction to the service used in the preceding section, you can see client-to-service communication is well-covered by the command approach and the bind approach. The reverse flow, from the service to a client, is far less structured, although there are enough options to cover almost any scenario you can imagine. Let’s cover the main options so you form an appreciation of what to use in various situations.

Use bindService Methods for All Communication

The first and most obvious option for service-to-client communication is to have all interaction happen via bindService() and the methods you create for clients to use. The advantage of the bind approach is that you control exactly what the client receives by the returned objects and information from your service methods, and this guarantees that the client actually gets what it asks for.

The obvious disadvantage is that any client that chooses to use the command-style approach to interacting, by firing off startService() calls, gets no such feedback mechanism for communication. This might not sound like a drawback now, but remember that you should design services to serve many different types of clients, so in fact this limitation hinders many possible users of your service.

Intents and Broadcast Receivers

Android already has a general approach for one component to signal or communicate with another component using broadcast intents and receivers. Cast you mind back to Chapter 12, and you should remember that you can trigger a broadcast intent from your code; you can do this just as easily from a service as from an activity.

This action allows you to register a BroadcastReceiver object via registerReceiver() in a client and capture a component-specific broadcast from the service, or some action imperative that you document so that the client can differentiate broadcast intents and process them accordingly.

The main drawback to this approach is that the intent must be action-orientated, rather than simply hoping for some activity to volunteer to act on a component. Additionally, in this case, you are assuming that the client activity itself is still running with its receiver and that it hasn’t been paused or chosen for destruction due to other device-level events or resource constraints.

Use PendingIntent Objects

Android provides PendingIntent objects as a way of signifying an intent with an associated action that needs to be performed. In the service realm, your client uses onActivityResult() to deal with the down-stream logic once the service performs its work. The client passes the PendingIntent object to the service by adding it as an intent extra in the startService() call, and the service itself signals the client by calling the send() method on it.

The main disadvantage to this approach is the extra client code you need to interpret the variety of send() invocations that can be used, and that you need to identify exactly which one was called.

Use Messenger and Message Objects

As if PendingIntent objects were not enough, Android also provides the Messenger object to facilitate intercontext communication, such as from a service to an activity. Individual activities all have a Handler object that they can use to send messages to themselves, but the Handler is not exposed for activity-to-activity or activity-to-service interaction. A Messenger object can send messages to any Handler, and thus such objects can be used as intermediaries to bridge the gap.

To use a Messenger object, add it as an extra to your intent prior to service invocation. The service receives the intent as normal and can extract the Messenger object from the extras; when the time comes to communicate to the client, the service creates and populates a Message object, and then it calls the .send() method on the Messenger passing the Message as a parameter. Your client activity receives this via the Handler and its handleMessage() method.

The main drawbacks here are the additional steps you need to create and exchange Messenger and Message objects, and the fact that the handleMessage() method for your activity is processed on the main application thread, which means you want to keep message processing to a minimum, or at least hand off heavier processing to another thread you create.

Use Independent Messaging

A twist on the built-in option of Messenger objects is to use some external messaging or pub/sub system to deal with service-to-client communication. You might use such an approach when you don’t strictly need real-time communication, but you know you will need such communication eventually.

The external messaging can leverage not just other messaging systems on the Android device, but also Internet-based messaging systems, such as Google Cloud Messaging. Google Cloud Messaging is a large topic in its own right, but Google has provided a good set of Android examples for its use; you can find these on the developer website at https://developers.google.com/cloud-messaging/android.

Create Callbacks and Listeners

The preceding examples of Messenger and PendingIntent objects show how easy it is to attach objects to the intent extras that are fed to a service. The principle requirement for any such object is that it is Parcelable; you can create your own objects that meet this criterion, such as a callback or listener of your own.

The logic follows a flow of you defining your listener objects and having the client and service coded to deploy and drop listeners as needed at specific times when communication is required. The main problem with this approach is that it is difficult to coordinate registering and retracting listeners so that neither the service nor the client is left with dangling or defunct listeners when the other party is trying to communicate. At best, this just leads to failed communication; at worst, as defunct listeners build up, you consume large amounts of memory.

Use Notifications

Although services have no direct UI themselves, there are ways to have services communicate via a client application’s UI. One of these approaches involves the Notification object, which can present directly to the user. I cover notifications in depth in Chapter 23, so I will save the discussion until then.

Services in Action

Now that you are familiar with the theory and fundamentals of services and their behavior with Android, let’s examine an example service and client application. There are a range of classic patterns for which services are used, such as audio playback, message delivery, management of a long-running download that doesn’t need user involvement, and so on. For the purposes of having a useful but simple example, I mock up a photo-sharing service and simple client example application to demonstrate service theory in action. Remember the focus here is on engineering a service to make the theory concrete rather than rivaling Flickr with actual photo sharing.

Choosing the Service Design

The nature of our example photo sharing lends itself to the startService() model of firing off a command to “share” and not needing an ongoing to-and-fro interaction with binding. Let’s devise our service from the base Service class and do the work of implementing the onStartCommand() method and the onBind() method (even though it is not used by our example client). Other subclasses provided with Android, such as IntentService, offer to cover much of the implementation for you, such as neatly calling stopService() automatically after your startService() call completes. In this case, we do not want or need some of those helpers—specifically, we want our service to live on after the startService() call so that we can later stop sharing if we want to.

The actual photos and who is sharing them are not important for the mechanics of how the service works, so I will leave those areas set out with comments. You are free to experiment and add images or other resources if you wish.

Implementing the Java Logic for Our Service

The ServiceExample implementation is relatively straightforward, covering the necessary implementation of overrides expected from the base Service class, plus our own service-specific logic for photo sharing. Listing 22-3 contains the full service implementation from the example in ch22/ClientExample.

Because we are not doing any service-specific setup at service startup, we can omit extra logic in an onCreate() call and rely on the parent class to take care of this. We implement onStartCommand() so that we can take the appropriate action when the client calls startService(). Specifically, we examine the intent used to designate the service and find out the extras the ServiceExample wants—namely the name of the photo album to share. With the album name, a call is then made to the startSharing() method implemented for this specific service.

The startSharing() method is mostly commented out, as I mentioned earlier. One useful thing we can do is deal with the fact that services have no user interface by logging relevant information at various service method points. In practice, this helps you with all kinds of debugging, usage metrics, and so on. In our example, by watching the output in LogCat, you can see that the service is actually running and being used. This can be really useful to you if, for example, you forget to add the <service> element to your manifest. A lack of log output hints that the service isn’t in use or even running.

Part way through this code, the onDestroy() method is implemented, and in this case, it simply calls our service’s stopSharing() method. Like startSharing(), this is mostly commented out with some logging to help you confirm that your service code is working and being reached.

Even though the client example shown later in this chapter does not attempt to bind to the service, we still need to implement onBind() based on our subclassing from Service. However, we can leave this returning null in this instance. Other clients you might write could conceivably want to bind to our photo-sharing service to do fancier things, like show a photo montage, loop through the album, and so forth.

Creating an Example Client for the Service

You can just leave the service as is, but then you are left dangling with no way to actually test to see that it does what I say it does or to try out any of the modifications you might want to make. Listing 22-4 provides a simple layout for a client to drive our ServiceExample service.

This is not a particular sophisticated UI, as you will see when you test it out. The ClientExample application has just two buttons—one labeled “Start Sharing” and one labeled “Stop Sharing”—with android:id attributes of startSharing and stopSharing, respectively.

Because we are using the command-style approach to the service, the Java logic is also quite straightforward. Listing 22-5 shows the complete client logic.

By now the basic onCreate() method should be familiar, inflating the layout. My onClick() implementation performs the usual check for which button the user pressed and triggers either the startSharing() or stopSharing() method as needed.

The startSharing() method instantiates an intent for the service and provides a very credible album name for the particular photos we want to share. The service is called with startService() passing the intent. The stopSharing() implementation simply calls the stopService() command with a new intent of the appropriate type to match the original service call and thus targets our service correctly for shutdown.

Testing the Service in Action

I will leave running the service and watching the results to you. Don’t forget to add the <service> entry to your manifest—for example,

<service android:name=".ServiceExample" />

If you do run the ClientExample application, when it triggers the calls to the ServiceExample service, you should see entries like this in LogCat.

...com.artifexdigital.android.clientexample.ServiceExample: Album successfully shared
...com.artifexdigital.android.clientexample.ServiceExample: Album sharing removed
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.134.75