Chapter 18. Started Services: At Your Service

There are some operations you want to keep on running, irrespective of which app has the focus. If you start downloading a file, for instance, you don’t want the download to stop when you switch to another app. In this chapter we’ll introduce you to started services, components that run operations in the background. You’ll see how to create a started service using the IntentService class, and find out how its lifecycle fits in with that of an activity. Along the way, you’ll discover how to log messages, and keep users informed using Android’s built-in notification service.

Services work in the background

An Android app is a collection of activities and other components. The bulk of your app’s code is there to interact with the user, but sometimes you need to do things in the background, such as download a large file, stream a piece of music, or listen for a message from the server.

These kinds of tasks aren’t what activities are designed to do. In simple cases, you can create a thread, but if you’re not careful your activity code will start to get complex and unreadable.

In addition to writing your own services, you can use Android’s built-in ones.

Built-in services include the notification service, location service, alarm service, and download service.

That’s why services were invented. A service is an application component like an activity but without a user interface. They have a simpler lifecycle than activities, and they come with a bunch of features that make it easy to write code that will run in the background while the user is doing something else.

There are three types of service

Services come in three main flavors:

  • Started services

    A started service can run in the background indefinitely, even when the activity that started it is destroyed. If you wanted to download a large file from the Internet, you would probably create it as a started service. Once the operation is done, the service stops.

  • Bound services

    A bound service is bound to another application component such as an activity. The activity can interact with it, send requests, and get results. A bound service runs as long as components are bound to it. When the components are no longer bound, the service is destroyed. If you wanted to create an odometer to measure the distance traveled by a vehicle, for example, you’d probably use a bound service. This way, any activities bound to the service could keep asking the service for updates on the distance traveled.

  • Scheduled services

    A scheduled service is one that’s scheduled to run at a particular time. As an example, from API 21, you can schedule jobs to run at an appropriate time.

In this chapter, we’re going to look at how you create a started service.

We’ll create a STARTED service

We’re going to create a new project that contains an activity called MainActivity, and a started service called DelayedMessageService. Whenever MainActivity calls DelayedMessageService, it will wait for 10 seconds and then display a piece of text.

We’re going to do this in two stages:

  1. Display the message in Android’s log.

    We’ll start by displaying the message in Android’s log so that we can check that the service works OK. We can look at the log in Android Studio.

  2. Display the message in a notification.

    We’ll get DelayedMessageService to use Android’s built-in notification service to display the message in a notification.

Create the project

We’ll start by creating the project. Create a new Android project for an application named “Joke” with a company domain of “hfad.com”, making the package name com.hfad.joke. The minimum SDK should be API 19 so that it will work with most devices. You’ll need an empty activity named “MainActivity” and a layout named “activity_main” so that your code matches ours. Make sure that you uncheck the Backwards Compatibility (AppCompat) option when you create the activity.

The next thing we need to do is create the service.

Use the IntentService class to create a basic started service

The simplest way of creating a started service is to extend the IntentService class, as it provides you with most of the functionality you need. You start it with an intent, and it runs the code that you specify in a separate thread.

We’re going to add a new intent service to our project. To do this, switch to the Project view of Android Studio’s explorer, click on the com.hfad.joke package in the app/src/main/java folder, go to File→New..., and select the Service option. When prompted, choose the option to create a new Intent Service. Name the service “DelayedMessageService” and uncheck the option to include helper start methods to minimize the amount of code that Android Studio generates for you. Click on the Finish button, then replace the code in DelayedMessageService.java with the code here:

The above code is all you need to create a basic intent service. You extend the IntentService class, add a public constructor, and implement the onHandleIntent() method.

The onHandleIntent() method should contain the code you want to run each time the service is passed an intent. It runs in a separate thread. If it’s passed multiple intents, it deals with them one at a time.

We want DelayedMessageService to display a message in Android’s log, so let’s look at how you log messages.

How to log messages

Adding messages to a log can be a useful way of checking that your code works the way you want. You tell Android what to log in your Java code, and when the app’s running, you check the output in Android’s log (a.k.a. logcat).

You log messages using one of the following methods in the Android.util.Log class:

Log.v(String tag, String message)

Logs a verbose message.

Log.d(String tag, String message)

Logs a debug message.

Log.i(String tag, String message)

Logs an information message.

Log.w(String tag, String message)

Logs a warning message.

Log.e(String tag, String message)

Logs an error message.

Note

There’s also a Log.wtf() method you can use to report exceptions that should never happen. According to the Android documentation, wtf means “What a Terrible Failure.” We know it really means “Welcome to Fiskidagurinn,” which refers to the Great Fish Day festival held annually in Dalvik, Iceland. Android developers can often be heard to say, “My AVD just took 8 minutes to boot up. WTF??” as a tribute to the small town that gave its name to the standard Android executable bytecode format.

Each message is composed of a String tag you use to identify the source of the message, and the message itself. As an example, to log a verbose message that’s come from DelayedMessageService, you use the Log.v() method like this:

Log.v("DelayedMessageService", "This is a message");

You can view the logcat in Android Studio and filter by the different types of message. To see the logcat, select the Android Monitor option at the bottom of your project screen in Android Studio and then select the logcat tab:

The full DelayedMessageService code

We want our service to get a piece of text from an intent, wait for 10 seconds, then display the piece of text in the log. To do this, we’ll add a showText() method to log the text, and then call it from the onHandleIntent() method after a delay of 10 seconds.

Here’s the full code for DelayedMessageService.java (update your version of the code to reflect our changes):

You declare services in AndroidManifest.xml

Just like activities, each service needs to be declared in AndroidManifest.xml so that Android can call it; if a service isn’t declared in this file, Android won’t know it’s there and won’t be able to call it.

Android Studio should update AndroidManifest.xml for you automatically whenever you create a new service by adding a new <service> element. Here’s what our AndroidManifest.xml code looks like:

The <service> element contains two attributes: name and exported. The name attribute tells Android what the name of the service is—in our case, DelayedMessageService. The exported attribute tells Android whether the service can be used by other apps. Setting it to false means that the service will only be used within the current app.

Now that we have a service, let’s get MainActivity to start it.

Add a button to activity_main.xml

We’re going to get MainActivity to start DelayedMessageService whenever a button is clicked, so we’ll add the button to MainActivity’s layout.

First, add the following values to strings.xml:

Then, replace your activity_main.xml code with ours below so that MainActivity displays a button:

The button will call an onClick() method whenever the user clicks it, so we’ll add this method to MainActivity.

You start a service using startService()

We’ll use MainActivity’s onClick() method to start DelayedMessageService whenever the user clicks on the button. You start a service from an activity in a similar way to how you start an activity. You create an explicit intent that’s directed at the service you want to start, then use the startService() method in your activity to start it:

Here’s our code for MainActivity.java; update your version to match ours:

That’s all the code we need to get our activity to start the service. Before we take it for a test drive, let’s go through what happens when the code runs.

What happens when you run the app

Here’s what the code does when we run the app:

  1. MainActivity starts DelayedMessageService by calling startService() and passing it an intent.

    The intent contains the message MainActivity wants DelayedMessageService to display, in this case “Timing!”.

  2. When DelayedMessageService receives the intent, its onHandleIntent() method runs.

    DelayedMessageService waits for 10 seconds.

  3. DelayedMessageService logs the message.

  4. When DelayedMessageService has finished running, it’s destroyed.

Let’s take the app for a test drive so we can see it working.

Test drive the app

When you run the app, MainActivity is displayed. It contains a single button:

Press the button, switch back to Android Studio, and watch the logcat output in the bottom part of the IDE. After 10 seconds, the message “Timing!” should appear in the logcat.

Now that you’ve seen DelayedMessageService running, let’s look in more detail at how started services work.

The states of a started service

When an application component (such as an activity) starts a service, the service moves from being created to running to being destroyed.

A started service spends most of its life in a running state; it’s been started by another component such as an activity, and it runs code in the background. It continues to run even if the component that started it is destroyed. When the service has finished running code, it’s destroyed.

A started service runs after it’s been started.

onCreate() gets called when the service is first created, and it’s where you do any service setup.

onDestroy() gets called just before the service gets destroyed.

Just like an activity, when a service moves from being created to being destroyed, it triggers key service lifecycle methods, which it inherits.

When the service is created, its onCreate() method gets called. You override this method if you want to perform any tasks needed to set up the service.

When the service is ready to start, its onStartCommand() method is called. If you’re using an IntentService (which is usually the case for a started service), you don’t generally override this method. Instead, you add any code you want the service to run to its onHandleIntent() method, which is called after onStartCommand().

The onDestroy() method is called when the started service is no longer running and it’s about to be destroyed. You override this method to perform any final cleanup tasks, such as freeing up resources.

We’ll take a closer look at how these methods fit into the service states on the next page.

The started service lifecycle: from create to destroy

Here’s an overview of the started service lifecycle from birth to death.

The onCreate(), onStartCommand(), and onDestroy() methods are three of the main service lifecycle methods. So where do these methods come from?

Your service inherits the lifecycle methods

As you saw earlier in the chapter, the started service you created extends the android.app.IntentService class. This class gives your service access to the Android lifecycle methods. Here’s a diagram showing the class hierarchy:

Now that you understand more about how started services work behind the scenes, have a go at the following exercise. After that, we’ll look at how we can make DelayedMessageService display its message in a notification.

Android has a built-in notification service

We’re going to change our Joke app so that our message gets displayed in a notification. Notifications are messages that are displayed outside the app’s user interface. When a notification gets issued, it’s displayed as an icon in the notification area of the status bar. You can see details of the notification in the notification drawer, which you access by swiping down from the top of the screen:

Unlike a toast or snackbar, notifications are available outside the app that issues them, so the user can access them no matter what app they’re currently using (if any). They’re much more configurable than toasts and snackbars, too.

To display the notification, we’re going to use one of Android’s built-in services, the notification service. You’ll see how to do this over the next few pages.

We’ll use notifications from the AppCompat Support Library

We’re going to create notifications using classes from the AppCompat Support Library so that our notifications will work consistently across a wide range of Android versions. While it’s possible to create notifications using classes from the main release of Android, recent changes to these classes mean that the newest features won’t be available on older versions.

Before we can use the notification classes from the Support Library, we need to add it to our project as a dependency. To do this, choose File→Project Structure, then click on the app module and choose Dependencies. Android Studio may have already added the AppCompat Support Library for you automatically. If so, you will see it listed as appcompat-v7. If it hasn’t been added, you will need to add it yourself. Click on the “+” button at the bottom or right side of the screen, choose the Library Dependency option, select the appcompat-v7 library, and then click on the OK button. Click on OK again to save your changes and close the Project Structure window.

Use notifications from the AppCompat Support Library to allow apps running on older versions of Android to include the newest features.

To get DelayedMessageService to display a notification, there are three things we need to do: create a notification builder, tell the notification to start MainActivity when it’s clicked, and issue the notification. We’ll build up the code over the next few pages, then show you the full code at the end.

First create a notification builder

The first thing we need to do is create a notification builder. This enables you to build a notification with specific content and features.

Each notification you create must include a small icon, a title, and some text as a bare minimum. Here’s the code to do that:

To add more features to the notification, you simply add the appropriate method call to the builder. As an example, here’s how you additionally specify that the notification should have a high priority, vibrate the device when it appears, and disappear when the user clicks on it:

These are just some of the properties that you can set. You can also set properties such as the notification’s visibility to control whether it appears on the device lock screen, a number to display in case you want to send many notifications from the same app, and whether it should play a sound. You can find out more about these properties (and many others) here:

https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html

Next, we’ll add an action to the notification to tell it which activity to start when it’s clicked.

You create a heads-up notification (one that appears in a small floating window) by setting its priority to high, and making it vibrate the device or play a sound.

Add an action to tell the notification which activity to start when clicked

When you create a notification, it’s a good idea to add an action to it, specifying which activity in your app should be displayed when the user clicks on the notification. As an example, an email app might issue a notification when the user receives a new email, and display the contents of that email when the user clicks on it. In our particular case, we’re going to start MainActivity.

You add an action by creating a pending intent to start an activity, which you then add to the notification. A pending intent is an intent that your app can pass to other applications. The application can then submit the intent on your app’s behalf at a later time.

To create a pending intent, you first create an explicit intent directed to the activity you want to start when the notification is clicked. In our case, we want to start MainActivity, so we use:

We then use that intent to create a pending intent using the PendingIntent.getActivity() method.

The getActivity() method takes four parameters: a context (usually this), an int request code, the explicit intent we defined above, and a flag that specifies the pending intent’s behavior. In the above code, we’ve used a flag of FLAG_UPDATE_CURRENT. This means that if a matching pending intent already exists, its extra data will be updated with the contents of the new intent. Other options are FLAG_CANCEL_CURRENT (cancel any existing matching pending intents before generating a new one), FLAG_NO_CREATE (don’t create the pending intent if there’s no matching existing one), and FLAG_ONE_SHOT (you can only use the pending intent once).

Once you’ve created the pending intent, you add it to the notification using the notification builder setContentIntent() method:

This tells the notification to start the activity specified in the intent when the user clicks on the notification.

Issue the notification using the built-in notification service

Finally, you issue the notification using Android’s notification service.

To do this, you first need to get a NotificationManager. You do this by calling the getSystemService() method, passing it a parameter of NOTIFICATION_SERVICE:

You then use the notification manager to issue the notification by calling its notify() method. This takes two parameters: a notification ID and a Notification object.

The notification ID is used to identify the notification. If you send another notification with the same ID, it will replace the current notification. This is useful if you want to update an existing notification with new information.

You create the Notification object by calling the notification builder’s build() method. The notification it builds includes all the content and features you’ve specified via the notification builder.

Here’s the code to issue the notification:

That’s everything we need to create and issue notifications. We’ll show you the full code for DelayedMessageService on the next page.

The full code for DelayedMessageService.java

Here’s the full code for DelayedMessageService.java. It now uses a notification to display a message to the user. Update your code to match ours:

The code continues on the next page.

The DelayedMessageService.java code (continued)

That’s all the code we need for our started service. Let’s go through what happens when the code runs.

What happens when you run the code

Before you try running the updated app, let’s go through what happens when the code runs:

  1. MainActivity starts DelayedMessageService by calling startService() and passing it an intent.

    The intent contains the message MainActivity wants DelayedMessageService to display.

  2. DelayedMessageService waits for 10 seconds.

  3. DelayedMessageService creates a notification builder and sets details of how the notification should be configured.

  4. DelayedMessageService creates an intent for MainActivity, which it uses to create a pending intent.

The story continues

  1. DelayedMessageService adds the pending intent to the notification builder.

  2. DelayedMessageService creates a NotificationManager object and calls its notify() method.

    The notification service displays the notification built by the notification builder.

  3. When the user clicks on the notification, the notification uses its pending intent to start MainActivity.

Now that we’ve gone through what the code does, let’s take the app for a test drive.

Test drive the app

When you click on the button in MainActivity, a notification is displayed after 10 seconds. You’ll receive the notification irrespective of which app you’re in.

When you open the notification drawer and click on the notification, Android returns you to MainActivity.

You now know how to create a started service that displays a notification using the Android notification service. In the next chapter, we’ll look at how you create a bound service.

Chapter 18 Your Android Toolbox

You’ve got Chapter 18 under your belt and now you’ve added started services to your toolbox.

Note

You can download the full code for the chapter from https://tinyurl.com/HeadFirstAndroid.

..................Content has been hidden....................

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