Chapter 12
Developing Android Services

A service is an application in Android that runs in the background without needing to interact with the user. For example, while using an application, you might want to play some background music at the same time. In this case, the code that is playing the background music has no need to interact with the user; therefore, it can be run as a service. Also, services are ideal for situations in which there is no need to present a user interface (UI) to the user. A good example of this scenario is an application that continually logs the geographical coordinates of the device. In this case, you can write a service to do that in the background. In this chapter, you find out how to create your own services and use them to perform background tasks asynchronously.

CREATING YOUR OWN SERVICES

The best way to understand how a service works is by creating one. The following Try It Out shows you the steps to create a simple service. Subsequent sections add more functionality to this service. For now, you see how to start and stop a service.

Performing Long-Running Tasks in a Service

Because the service you created in the previous section does not do anything useful, in this section you modify it so that it performs a task. In the following Try It Out, you simulate the service of downloading a file from the Internet.

That means for a long-running service, it is important that you put all long-running code into a separate thread so that it does not tie up the application that calls it. The following Try It Out shows you how.

Performing Repeated Tasks in a Service

In addition to performing long-running tasks in a service, you might also perform some repeated tasks in a service. For example, you could write an alarm clock service that runs persistently in the background. In this case, your service might need to periodically execute some code to check whether a prescheduled time has been reached so that an alarm can be sounded. To execute a block of code to be executed at a regular time interval, you can use the Timer class within your service. The following Try It Out shows you how.

Executing Asynchronous Tasks on Separate Threads Using IntentService

Earlier in this chapter, you learned how to start a service using the startService() method and stop a service using the stopService() method. You have also seen how you should execute long-running tasks on a separate thread—not the same thread as the calling activities. It is important to note that once your service has finished executing a task, it should be stopped as soon as possible so that it does not unnecessarily hold up valuable resources. That's why you use the stopSelf() method to stop the service when a task has been completed. Unfortunately, a lot of developers often forget to terminate a service when it is done performing its task. To easily create a service that runs a task asynchronously and terminates itself when it is done, you can use the IntentService class.

The IntentService class is a base class for Service that handles asynchronous requests on demand. It is started just like a normal service; and it executes its task within a worker thread and terminates itself when the task is completed. The following Try It Out demonstrates how to use the IntentService class.

ESTABLISHING COMMUNICATION BETWEEN A SERVICE AND AN ACTIVITY

Often a service simply executes in its own thread, independently of the activity that calls it. This doesn't pose a problem if you simply want the service to perform some tasks periodically and the activity does not need to be notified about the service's status. For example, you might have a service that periodically logs the geographical location of the device to a database. In this case, there is no need for your service to interact with any activities, because its main purpose is to save the coordinates into a database. However, suppose you want to monitor for a particular location. When the service logs an address that is near the location you are monitoring, it might need to communicate that information to the activity. If so, you need to devise a way for the service to interact with the activity.

The following Try It Out demonstrates how a service can communicate with an activity using a BroadcastReceiver.

BINDING ACTIVITIES TO SERVICES

So far, you have seen how services are created, how they are called, and how they are terminated when they are done with their task. All the services that you have seen are simple—either they start with a counter and increment at regular intervals or they download a fixed set of files from the Internet. However, real-world services are usually much more sophisticated, requiring the passing of data so that they can do the job correctly for you.

Using the service demonstrated earlier that downloads a set of files, suppose you now want to let the calling activity determine what files to download, instead of hardcoding them in the service. Here is what you need to do.

  1. First, in the calling activity, you create an Intent object, specifying the service name:
        public void startService(View view) {
            Intent intent = new Intent(getBaseContext(), MyService.class);
        }
  2. You then create an array of URL objects and assign it to the Intent object through its putExtra() method.
  3. You start the service using the Intent object:
        public void startService(View view) {
            Intent intent = new Intent(getBaseContext(), MyService.class);
            try {
                URL[] urls = new URL[] {
                        new URL("http://www.amazon.com/somefiles.pdf"),
                        new URL("http://www.wrox.com/somefiles.pdf"),
                        new URL("http://www.google.com/somefiles.pdf"),
                        new URL("http://www.learn2develop.net/somefiles.pdf")};
                intent.putExtra("URLs", urls);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            startService(intent);
        }
  4. Note that the URL array is assigned to the Intent object as an Object array.
  5. On the service's end, you need to extract the data passed in through the Intent object in the onStartCommand() method:
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
            Object[] objUrls = (Object[]) intent.getExtras().get("URLs");
            URL[] urls = new URL[objUrls.length];
            for (int i=0; i<objUrls.length-1; i++) {
                urls[i] = (URL) objUrls[i];
            }
            new DoBackgroundTask().execute(urls);
            return START_STICKY;
        }
  6. The preceding first extracts the data using the getExtras() method to return a Bundle object.
  7. It then uses the get() method to extract the URL array as an Object array.
  8. Because in Java you cannot directly cast an array from one type to another, you must create a loop and cast each member of the array individually.
  9. Finally, you execute the background task by passing the URL array into the execute() method.

This is one way in which your activity can pass values to the service. As you can see, if you have relatively complex data to pass to the service, you must do some additional work to ensure that the data is passed correctly. A better way to pass data is to bind the activity directly to the service so that the activity can call any public members and methods on the service directly. The following Try It Out shows you how to bind an activity to a service.

UNDERSTANDING THREADING

So far, you have seen how services are created and why it is important to ensure that your long-running tasks are properly handled, especially when updating the UI thread. Earlier in this chapter (as well as in Chapter 9), you also saw how to use the AsyncTask class for executing long-running code in the background. This section briefly summarizes the various ways to handle long-running tasks correctly using a variety of methods available.

For this discussion, assume that you have an Android project named Threading. The main.xml file contains a Button and TextView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello"/>
    <Button
        android:id="@+id/btnStartCounter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start"
        android:onClick="startCounter"/>
    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView"/>
    </LinearLayout>

Suppose you want to display a counter on the activity, from 0 to 1,000. In your ThreadingActivity class, you have the following code:

package net.learn2develop.Threading;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class ThreadingActivity extends Activity {
    TextView txtView1;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        txtView1 = (TextView) findViewById(R.id.textView1);
    }
    public void startCounter(View view) {
        for (int i=0; i<=1000; i++) {
            txtView1.setText(String.valueOf(i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Log.d("Threading", e.getLocalizedMessage());
            }
        }
    }
}

When you run the application and click the Start button, the application is briefly frozen.

The UI freezes because the application is continuously trying to display the value of the counter at the same time it is pausing for one second after it has been displayed. This ties up the UI, which is waiting for the display of the numbers to be completed. The result is a nonresponsive application that will frustrate your users.

To solve this problem, one option is to wrap the part of the code that contains the loop using a Thread and Runnable class, like this:

    public void startCounter(View view) {
        new Thread(new Runnable() {
            public void run() {
                for (int i=0; i<=1000; i++) {
                    txtView1.setText(String.valueOf(i));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Log.d("Threading", e.getLocalizedMessage());
                    }
                }
            }
        }).start();
    }

In the preceding code, you first create a class that implements the Runnable interface. Within this class, you put your long-running code within the run() method. The Runnable block is then started using the Thread class.

However, the preceding application will not work, and it will crash if you try to run it. This code that is placed inside the Runnable block is on a separate thread, and in the preceding example you are trying to update the UI from another thread, which is not a safe thing to do because Android UIs are not thread-safe. To resolve this, you need to use the post() method of a View to create another Runnable block to be added to the message queue. In short, the new Runnable block created will be executed in the UI thread, so it would now be safe to execute your application:

    public void startCounter(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<=1000; i++) {
                    final int valueOfi = i;
                    //---update UI---
                    txtView1.post(new Runnable() {
                        public void run() {
                            //---UI thread for updating---
                            txtView1.setText(String.valueOf(valueOfi));
                        }
                    });
                    //---insert a delay
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Log.d("Threading", e.getLocalizedMessage());
                    }
                }
            }
        }).start();
    }

This application will now work correctly, but it is complicated and makes your code difficult to maintain.

A second option to update the UI from another thread is to use the Handler class. A Handler enables you to send and process messages, similar to using the post() method of a View. The following code snippet shows a Handler class called UIupdater that updates the UI using the message that it receives:

    //---used for updating the UI on the main activity---
    static Handler UIupdater = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            byte[] buffer = (byte[]) msg.obj;
            //---convert the entire byte array to string---
            String strReceived = new String(buffer);
            //---display the text received on the TextView---
            txtView1.setText(strReceived);
            Log.d("Threading", "running");
        }
    };
    public void startCounter(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<=1000; i++) {
                    //---update the main activity UI---
                    ThreadingActivity.UIupdater.obtainMessage(
                        0,  String.valueOf(i).getBytes() ).sendToTarget();
                    //---insert a delay
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Log.d("Threading", e.getLocalizedMessage());
                    }
                }
            }
        }).start();
    }
}

A detailed discussion of the Handler class is beyond the scope of this book. For more details, check out the documentation at https://developer.android.com/reference/android/os/Handler.html.

So far, the two methods just described enable you to update the UI from a separate thread. In Android, you could use the simpler AsyncTask class to do this. Using the AsyncTask, you could rewrite the preceding code as follows:

    private class DoCountingTask extends AsyncTask<Void, Integer, Void> {
        protected Void doInBackground(Void… params) {
            for (int i = 0; i < 1000; i++) {
                //---report its progress---
                publishProgress(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Log.d("Threading", e.getLocalizedMessage());
                }
            }
            return null;
        }
        protected void onProgressUpdate(Integer… progress) {
            txtView1.setText(progress[0].toString());
            Log.d("Threading", "updating…");
        }
    }
    public void startCounter(View view) {
        new DoCountingTask().execute();
    }

The preceding code will update the UI safely from another thread. What about stopping the task? If you run the preceding application and then click the Start button, the counter will start to display from zero. However, if you press the back button on the emulator/device, the task will continue to run even though the activity has been destroyed. You can verify this through the LogCat window. If you want to stop the task, use the following code snippets:

public class ThreadingActivity extends Activity {
    static TextView txtView1;
    DoCountingTask task;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        txtView1 = (TextView) findViewById(R.id.textView1);
    }
    public void startCounter(View view) {
        task = (DoCountingTask) new DoCountingTask().execute();
    }
    public void stopCounter(View view) {
        task.cancel(true);
    }
    private class DoCountingTask extends AsyncTask<Void, Integer, Void> {
        protected Void doInBackground(Void… params) {
            for (int i = 0; i < 1000; i++) {
                //---report its progress---
                publishProgress(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Log.d("Threading", e.getLocalizedMessage());
                }
                if (isCancelled()) break;
            }
            return null;
        }
        protected void onProgressUpdate(Integer… progress) {
            txtView1.setText(progress[0].toString());
            Log.d("Threading", "updating…");
        }
    }
    @Override
    protected void onPause() {
        super.onPause();
        stopCounter(txtView1);
    }
}

To stop the AsyncTask subclass, you need to get an instance of it first. To stop the task, call its cancel() method. Within the task, you call the isCancelled() method to check whether the task should be terminated.

SUMMARY

In this chapter, you learned how to create a service in your Android project to execute long-running tasks. You have seen the many approaches you can use to ensure that the background task is executed in an asynchronous fashion, without tying up the main calling activity. You have also learned how an activity can pass data into a service, and how you can alternatively bind to an activity so that it can access a service more directly.

EXERCISES

  1. Why is it important to put long-running code in a service on a separate thread?
  2. What is the purpose of the IntentService class?
  3. Name the three methods you need to implement in an AsyncTask class.
  4. How can a service notify an activity of an event happening?
  5. For threading, what is the recommended method to ensure that your code runs without tying up the UI of your application?

    You can find answers to the exercises in the appendix.

WHAT YOU LEARNED IN THIS CHAPTER

TOPIC KEY CONCEPTS
Creating a service Create a class and extend the Service class.
Implementing the methods in a service Implement the following methods: onBind(), onStartCommand(), and onDestroy().
Starting a service Use the startService() method.
Stopping a service Use the stopService() method.
Performing long-running tasks Use the AsyncTask class and implement three methods: doInBackground(), onProgressUpdate(), and onPostExecute().
Performing repeated tasks Use the Timer class and call its scheduleAtFixedRate() method.
Executing tasks on a separate thread and auto-stopping a service Use the IntentService class.
Enabling communication between an activity and a service Use the Intent object to pass data into the service. For a service, broadcast an Intent to notify an activity.
Binding an activity to a service Use the Binder class in your service and implement the ServiceConnection class in your calling activity.
Updating the UI from a Runnable block Use the post() method of a view to update the UI. Alternatively, you can also use a Handler class. The recommended way is to use the AsyncTask class.