Working in the Background - Professional Android 4 Application Development (2012)

Professional Android 4 Application Development (2012)

Chapter 9. Working in the Background

What's in this Chapter?

Creating, starting, and stopping Services

Binding Services to Activities

Creating ongoing foreground Services

Extending the Intent Service

Using AsyncTasks to manage background processing

Creating background Threads and using Handlers to synchronize with the GUI Thread

Using Alarms to schedule application events

Android offers the Service class to create application components that handle long-lived operations and include functionality that doesn't require a user interface.

Android accords Services a higher priority than inactive Activities, so they're less likely to be killed when the system requires resources. In fact, should the run time prematurely terminate a Service that's been started, it can be configured to restart as soon as sufficient resources become available. When necessary a Service's priority can be raised to the equivalent of a foreground Activity. This is reserved for extreme cases, where the termination of a Service will noticeably affect the user experience—such as an interruption in music playback.

By using Services, you can ensure that your applications can continue to run even when their UI isn't visible.

Although Services run without a dedicated GUI, they still execute in the main Thread of the application's process—just like Activities and Broadcast Receivers. To keep your applications responsive, you'll learn to move time-consuming processes onto background Threads using the Thread andAsyncTask classes.

This chapter also introduces Alarms, a mechanism for firing Intents at set intervals or set times, outside the scope of your application's lifecycle. You'll learn to use Alarms to start Services, open Activities, or broadcast Intents based on either the clock time or the time elapsed since device boot. An Alarm will fire even after its owner application has been closed and can (if required) wake a device from sleep.

Introducing Services

Unlike Activities, which display graphical interfaces, Services run invisibly—doing Internet lookups, processing data, updating your Content Providers, firing Intents, and triggering Notifications. While Activities are started, stopped, and re-created regularly as part of their lifecycle, Services are designed to be longer-lived—specifically, to perform ongoing and potentially time-consuming operations.

Services are started, stopped, and controlled from other application components, including Activities, Broadcast Receivers, and other Services. If your application provides functionality that doesn't depend directly on user input, or includes time-consuming operations, Services may be the answer.

Running Services have a higher priority than inactive or invisible (stopped) Activities, making them less likely to be terminated by the run time's resource management. The only reason Android will stop a Service prematurely is to provide additional resources for a foreground component (usually an Activity). When that happens, your Service can be configured to restart automatically when resources become available.

If your Service is interacting directly with the user (for example, by playing music), it may be necessary to increase its priority by labeling it as a foreground component. This will ensure that your Service isn't terminated except in extreme circumstances, but it reduces the run time's ability to manage its resources, potentially degrading the overall user experience.

Creating and Controlling Services

The following sections describe how to create a new Service, and how to start and stop it using Intents with the startService and stopService methods, respectively. Later you'll learn how to bind a Service to an Activity to provide a richer interface.

Creating Services

To define a Service, create a new class that extends Service. You'll need to override the onCreate and onBind methods, as shown in Listing 9.1.

2.11

Listing 9.1: A skeleton Service class

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
 
public class MyService extends Service {
  @Override
  public void onCreate() {
    super.onCreate();
    // TODO: Actions to perform when service is created.
  }
 
  @Override
  public IBinder onBind(Intent intent) {
    // TODO: Replace with service binding implementation.
    return null;
  }
}

code snippet PA4AD_Ch09_MyService/src/MyService.java

After you've constructed a new Service, you must register it in the application manifest. Do this by including a service tag within the application node, as shown in Listing 9.2.

2.11

Listing 9.2: Adding a Service node to the application manifest

<service android:enabled="true" android:name=".MyService"/>

code snippet PA4AD_Ch09_MyService/AndroidManifest.xml

To ensure your Service can be started and stopped only by your own application, add a permission attribute to its Service node.

<service android:enabled="true" 
         android:name=".MyService"
         android:permission="com.paad.MY_SERVICE_PERMISSION"/>

This will require any third-party applications to include a uses-permission in their manifests in order to access this Service. You'll learn more about creating and using permissions in Chapter 18, “Advanced Android Development.”

Executing a Service and Controlling Its Restart Behavior

Override the onStartCommand event handler to execute the task (or begin the ongoing operation) encapsulated by your Service. You can also specify your Service's restart behavior within this handler.

The onStartCommand method is called whenever the Service is started using startService, so it may be executed several times within a Service's lifetime. You should ensure that your Service accounts for this.

2.1

The onStartCommand handler was introduced in Android 2.0 (API level 5) and replaces the deprecated onStart event. It provides the same functionality as the deprecated method, but in addition it enables you specify how to handle restarts if the Service is killed by the system prior to an explicit call to stopService or stopSelf.

Services are launched on the main Application Thread, meaning that any processing done in the onStartCommand handler will happen on the main GUI Thread. The standard pattern for implementing a Service is to create and run a new Thread from onStartCommand to perform the processing in the background, and then stop the Service when it's been completed. (You will be shown how to create and manage background Threads later in this chapter.)

Listing 9.3 extends the skeleton code shown in Listing 9.1 by overriding the onStartCommand handler. Note that it returns a value that controls how the system will respond if the Service is restarted after being killed by the run time.

2.11

Listing 9.3: Overriding Service restart behavior

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  startBackgroundTask(intent, startId);
  return Service.START_STICKY;
}

code snippet PA4AD_Ch09_MyService/src/MyService.java

This pattern lets onStartCommand complete quickly, and it enables you to control the restart behavior by returning one of the following Service constants:

· START_STICKY—Describes the standard behavior, which is similar to the way in which onStart was implemented prior to Android 2.0. If you return this value, onStartCommand will be called any time your Service restarts after being terminated by the run time. Note that on a restart the Intent parameter passed in to onStartCommand will be null.

This mode typically is used for Services that handle their own states and that are explicitly started and stopped as required (via startService and stopService). This includes Services that play music or handle other ongoing background tasks.

· START_NOT_STICKY—This mode is used for Services that are started to process specific actions or commands. Typically, they will use stopSelf to terminate once that command has been completed.

Following termination by the run time, Services set to this mode restart only if there are pending start calls. If no startService calls have been made since the Service was terminated, the Service will be stopped without a call being made to onStartCommand.

This mode is ideal for Services that handle specific requests, particularly regular processing such as updates or network polling. Rather than restarting the Service during a period of resource contention, it's often more prudent to let the Service stop and retry at the next scheduled interval.

· START_REDELIVER_INTENT—In some circumstances you will want to ensure that the commands you have requested from your Service are completed—for example when timeliness is important.

This mode is a combination of the first two; if the Service is terminated by the run time, it will restart only if there are pending start calls or the process was killed prior to its calling stopSelf. In the latter case, a call to onStartCommand will be made, passing in the initial Intent whose processing did not properly complete.

Note that each mode requires you to explicitly stop your Service, through a call to stopService or stopSelf, when your processing has completed. Both methods are discussed in more detail later in this chapter.

2.1

Prior to Android SDK 2.0 (API level 5), the Service class triggered the onStart event handler to let you perform actions when the Service started. Implementing the onStart handler is now the equivalent of overriding onStartCommand and returning START_STICKY.

The restart mode you specify in your onStartCommand return value will affect the parameter values passed in to it on subsequent calls. Initially, the Intent will be the parameter you passed in to startService to start your Service. After system-based restarts it will be either null, in the case ofSTART_STICKY mode, or the original Intent if the mode is set to START_REDELIVER_INTENT.

The flag parameter can be used to discover how the Service was started. In particular, you determine if either of the following cases is true:

· START_FLAG_REDELIVERY—Indicates that the Intent parameter is a redelivery caused by the system run time's having terminated the Service before it was explicitly stopped by a call to stopSelf.

· START_FLAG_RETRY—Indicates that the Service has been restarted after an abnormal termination. It is passed in when the Service was previously set to START_STICKY.

Starting and Stopping Services

To start a Service, call startService. Much like Activities, you can either use an action to implicitly start a Service with the appropriate Intent Receiver registered, or you can explicitly specify the Service using its class. If the Service requires permissions that your application does not have, the call to startService will throw a SecurityException.

In both cases you can pass values in to the Service's onStart handler by adding extras to the Intent, as shown in Listing 9.4, which demonstrates both techniques available for starting a Service.

2.11

Listing 9.4: Starting a Service

private void explicitStart() {
  // Explicitly start My Service
  Intent intent = new Intent(this, MyService.class);
  // TODO Add extras if required.
  startService(intent);
}
 
private void implicitStart() {
  // Implicitly start a music Service
  Intent intent = new Intent(MyMusicService.PLAY_ALBUM);
  intent.putExtra(MyMusicService.ALBUM_NAME_EXTRA, "United"); 
  intent.putExtra(MyMusicService.ARTIST_NAME_EXTRA, "Pheonix"); 
  startService(intent);    
} 

code snippet PA4AD_Ch9_MyService/src/MyActivity.java

To stop a Service, use stopService, specifying an Intent that defines the Service to stop (in the same way you specified which Service to start), as shown in Listing 9.5.

2.11

Listing 9.5: Stopping a Service

// Stop a service explicitly.
stopService(new Intent(this, MyService.class));
    
// Stop a service implicitly.
Intent intent = new Intent(MyMusicService.PLAY_ALBUM);
stopService(intent); 

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

Calls to startService do not nest, so a single call to stopService will terminate the running Service it matches, no matter how many times startService has been called.

Self-Terminating Services

Due to the high priority of Services, they are not commonly killed by the run time, so self-termination can significantly improve the resource footprint of your application.

By explicitly stopping the Service when your processing is complete, you allow the system to recover the resources otherwise required to keep it running.

When your Service has completed the actions or processing for which it was started, you should terminate it by making a call to stopSelf. You can call stopSelf either without a parameter to force an immediate stop, or by passing in a startId value to ensure processing has been completed for each instance of startService called so far.

Binding Services to Activities

Services can be bound to Activities, with the latter maintaining a reference to an instance of the former, enabling you to make method calls on the running Service as you would on any other instantiated class.

Binding is useful for Activities that would benefit from a more detailed interface with a Service. To support binding for a Service, implement the onBind method, returning the current instance of the Service being bound, as shown in Listing 9.6.

2.11

Listing 9.6: Implementing binding on a Service

@Override
public IBinder onBind(Intent intent) {
  return binder;
}
 
public class MyBinder extends Binder {
  MyMusicService getService() {
    return MyMusicService.this;
  }
}
private final IBinder binder = new MyBinder();

code snippet PA4AD_Ch09_MyService/src/MyMusicService.java

The connection between the Service and another component is represented as a ServiceConnection.

To bind a Service to another application component, you need to implement a new ServiceConnection, overriding the onServiceConnected and onServiceDisconnected methods to get a reference to the Service instance after a connection has been established, as shown in Listing 9.7.

Listing 9.7: Creating a Service Connection for Service binding

// Reference to the service
private MyMusicService serviceRef;
 
// Handles the connection between the service and activity
private ServiceConnection mConnection = new ServiceConnection() {
  public void onServiceConnected(ComponentName className, 
                                 IBinder service) {
    // Called when the connection is made.
    serviceRef = ((MyMusicService.MyBinder)service).getService();
  }
 
  public void onServiceDisconnected(ComponentName className) {
    // Received when the service unexpectedly disconnects.
    serviceRef = null;
  }
};

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

To perform the binding, call bindService within your Activity, passing in an Intent (either explicit or implicit) that selects the Service to bind to, and an instance of a ServiceConnection implementation. You can also specify a number of binding flags, as shown in Listing 9.8. In this example you specify that the target Service should be created when the binding is initiated.

2.11

Listing 9.8: Binding to a Service

// Bind to the service
Intent bindIntent = new Intent(MyActivity.this, MyMusicService.class);
bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

Android 4.0 (API level 14) introduced a number of new flags that can be used and combined when binding a Service to an application:

· BIND_ADJUST_WITH_ACTIVITY—Causes the Service's priority to be adjusted based on the relative importance of the Activity to which it is bound. As a result, the run time will increase the priority of the Service when the Activity is in the foreground.

· BIND_ABOVE_CLIENT and BIND_IMPORTANT—Specify that the bound Service is so important to the binding client that it should be become a foreground process when the client is in the foreground—in the case of BIND_ABOVE_CLIENT, you are specifying that the run time should terminate the Activity before the bound Service in cases of low memory.

· BIND_NOT_FOREGROUND—Ensures the bound Service is never brought to foreground priority. By default, the act of binding a Service increases its relative priority.

· BIND_WAIVE_PRIORITY—Indicates that binding the specified Service shouldn't alter its priority.

When the Service has been bound, all its public methods and properties are available through the serviceBinder object obtained from the onServiceConnected handler.

Android applications do not (normally) share memory, but in some cases your application may want to interact with (and bind to) Services running in different application processes.

You can communicate with a Service running in a different process by using broadcast Intents or through the extras Bundle in the Intent used to start the Service. If you need a more tightly coupled connection, you can make a Service available for binding across application boundaries by using Android Interface Definition Language (AIDL). AIDL defines the Service's interface in terms of OS-level primitives, allowing Android to transmit objects across process boundaries. AIDL definitions are covered in Chapter 18.

An Earthquake-Monitoring Service Example

In this chapter you'll modify the Earthquake example you started in Chapter 6 (and continued to enhance in Chapters 7 and 8). In this example you'll move the earthquake updating and processing functionality into its own Service component.

2.1

In the sections “Using Repeating Alarms to Schedule Network Refreshes” and “Using the Intent Service to Simplify the Earthquake Update Service,” you'll extend this Service by improving its efficiency and simplifying the implementation.

1. Start by creating a new EarthquakeUpdateService that extends Service:

package com.paad.earthquake;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
 
public class EarthquakeUpdateService extends Service {
 
  public static String TAG = "EARTHQUAKE_UPDATE_SERVICE";
 
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
}

2. Add this new Service to the manifest by adding a new service tag within the application node:

<service android:enabled="true" android:name=".EarthquakeUpdateService"/>

3. Move the addNewQuake method out of the EarthquakeListFragment and into the EarthquakeUpdateService. Modify the first line in the method to obtain the Content Resolver from the Service rather than a parent Activity:

private void addNewQuake(Quake quake) {
  ContentResolver cr = getContentResolver();
 
  // Construct a where clause to make sure we don't already have this
  // earthquake in the provider.
  String w = EarthquakeProvider.KEY_DATE + " = " + quake.getDate().getTime();
 
  // If the earthquake is new, insert it into the provider.
  Cursor query = cr.query(EarthquakeProvider.CONTENT_URI, null, w, null, null);
  
  if (query.getCount()==0) {
    ContentValues values = new ContentValues();
 
    values.put(EarthquakeProvider.KEY_DATE, quake.getDate().getTime());
    values.put(EarthquakeProvider.KEY_DETAILS, quake.getDetails());   
    values.put(EarthquakeProvider.KEY_SUMMARY, quake.toString());
 
    double lat = quake.getLocation().getLatitude();
    double lng = quake.getLocation().getLongitude();
    values.put(EarthquakeProvider.KEY_LOCATION_LAT, lat);
    values.put(EarthquakeProvider.KEY_LOCATION_LNG, lng);
    values.put(EarthquakeProvider.KEY_LINK, quake.getLink());
    values.put(EarthquakeProvider.KEY_MAGNITUDE, quake.getMagnitude());
 
    cr.insert(EarthquakeProvider.CONTENT_URI, values);
  }
  query.close();
}

4. Create a new refreshEarthquakes method in the EarthquakeUpdateService. You will move most of the functionality from the method of the same name in the Earthquake List Fragment into this new method:

public void refreshEarthquakes() {
}

4.1 Start by moving all the XML processing code into the Service's refreshEarthquakes method:

public void refreshEarthquakes() {
  // Get the XML
  URL url;
  try {
    String quakeFeed = getString(R.string.quake_feed);
    url = new URL(quakeFeed);
 
    URLConnection connection;
    connection = url.openConnection();
 
    HttpURLConnection httpConnection = (HttpURLConnection)connection;
    int responseCode = httpConnection.getResponseCode();
 
    if (responseCode == HttpURLConnection.HTTP_OK) {
      InputStream in = httpConnection.getInputStream();
 
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
 
      // Parse the earthquake feed.
      Document dom = db.parse(in);
      Element docEle = dom.getDocumentElement();
 
      // Get a list of each earthquake entry.
      NodeList nl = docEle.getElementsByTagName("entry");
      if (nl != null && nl.getLength() > 0) {
        for (int i = 0 ; i < nl.getLength(); i++) {
          Element entry = (Element)nl.item(i);
          Element title = (Element)entry.getElementsByTagName("title").item(0);
          Element g = (Element)entry.getElementsByTagName("georss:point").item(0);
          Element when = (Element)entry.getElementsByTagName("updated").item(0);
          Element link = (Element)entry.getElementsByTagName("link").item(0);
 
          String details = title.getFirstChild().getNodeValue();
          String hostname = "http://earthquake.usgs.gov";
          String linkString = hostname + link.getAttribute("href");
 
          String point = g.getFirstChild().getNodeValue();
          String dt = when.getFirstChild().getNodeValue(); 
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
          Date qdate = new GregorianCalendar(0,0,0).getTime();
          try {
            qdate = sdf.parse(dt);
          } catch (ParseException e) {
            Log.e(TAG, "Date parsing exception.", e);
          }
 
          String[] location = point.split(" ");
          Location l = new Location("dummyGPS");
          l.setLatitude(Double.parseDouble(location[0]));
          l.setLongitude(Double.parseDouble(location[1]));
 
          String magnitudeString = details.split(" ")[1];
          int end =  magnitudeString.length()-1;
          double magnitude = Double.parseDouble(magnitudeString.substring(0, end));
 
          details = details.split(",")[1].trim();
 
          Quake quake = new Quake(qdate, details, l, magnitude, linkString);
 
          // Process a newly found earthquake
          addNewQuake(quake);
        }
      }
    }
  } catch (MalformedURLException e) {
    Log.e(TAG, "Malformed URL Exception", e);
  } catch (IOException e) {
    Log.e(TAG, "IO Exception", e);
  } catch (ParserConfigurationException e) {
    Log.e(TAG, "Parser Configuration Exception", e);
  } catch (SAXException e) {
    Log.e(TAG, "SAX Exception", e);
  }
  finally {
  }

4.2 With the XML processing moved out of the refreshEarthquakes method in the Earthquake List Fragment, there's no longer a need to execute it on a background thread. Update the onActivityCreated handler to remove the Thread creation code:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
 
  // Create a new Adapter and bind it to the List View
  adapter = new SimpleCursorAdapter(getActivity(),
    android.R.layout.simple_list_item_1, null,
    new String[] { EarthquakeProvider.KEY_SUMMARY },
    new int[] { android.R.id.text1 }, 0);
  setListAdapter(adapter);
 
  getLoaderManager().initLoader(0, null, this);
  
  refreshEarthquakes();
}

4.3 The Earthquake List Fragment's refreshEarthquake method should still contain the code used to restart the Cursor Loader, but it no longer needs to synchronize it to the UI thread. Remove that code and add a new call to startService that will explicitly start theEarthquakeUpdateService:

public void refreshEarthquakes() {
  getLoaderManager().restartLoader(0, null, EarthquakeListFragment.this); 
 
  getActivity().startService(new Intent(getActivity(), 
                                        EarthquakeUpdateService.class));
}

5. Return to the EarthquakeService. Override the onStartCommand and onCreate methods to refresh the earthquake from the server, and to create a new Timer that will be used to regularly update the earthquake list.

The onStartCommand handler should return START_STICKY because you are using a timer to trigger multiple refreshes. This is generally poor form—the Timer behavior should be triggered by Alarms and/or an Intent Service. You'll learn how to do both of these things later in this chapter. Use the SharedPreference object created in Chapter 7 to determine if the earthquakes should be updated regularly.

private Timer updateTimer;
 
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  // Retrieve the shared preferences
  Context context = getApplicationContext();
  SharedPreferences prefs = 
    PreferenceManager.getDefaultSharedPreferences(context);
 
  int updateFreq = 
    Integer.parseInt(prefs.getString(PreferencesActivity.PREF_UPDATE_FREQ, "60"));
  boolean autoUpdateChecked = 
    prefs.getBoolean(PreferencesActivity.PREF_AUTO_UPDATE, false);
 
  updateTimer.cancel();
  if (autoUpdateChecked) {
    updateTimer = new Timer("earthquakeUpdates");
    updateTimer.scheduleAtFixedRate(doRefresh, 0,
      updateFreq*60*1000);
  }
  else {
    Thread t = new Thread(new Runnable() {
      public void run() {
        refreshEarthquakes(); 
      }
    });
    t.start();
  }
 
  return Service.START_STICKY;
};
 
private TimerTask doRefresh = new TimerTask() {
  public void run() {
    refreshEarthquakes();
  }
};
 
@Override
public void onCreate() {
  super.onCreate();
  updateTimer = new Timer("earthquakeUpdates");
}

2.1

All code snippets in this example are part of the Chapter 9 Earthquake Part 1 project, available for download at www.wrox.com.

Now when the Earthquake Activity is launched, it will start the Earthquake Service. This Service will then continue to run, updating the Content Provider in the background, even after the Activity is suspended or closed. Because the Earthquake List Fragment is using a Cursor Loader, each new Earthquake will automatically be added to the List View.

2.1

At this stage the earthquake Service is constantly running, taking up valuable resources. Later sections will explain how to replace the Timer with Alarms and the Intent Service.

Creating Foreground Services

As you learned in Chapter 3, “Creating Applications and Activities,” Android uses a dynamic approach to managing resources that can result in your application's components being terminated with little or no warning.

When calculating which applications and application components should be killed, Android assigns running Services the second-highest priority. Only active, foreground Activities are considered a higher priority.

In cases where your Service is interacting directly with the user, it may be appropriate to lift its priority to the equivalent of a foreground Activity's. You can do this by setting your Service to run in the foreground by calling its startForeground method.

Because foreground Services are expected to be interacting directly with the user (for example, by playing music), calls to startForeground must specify an ongoing Notification (described in more detail in Chapter 10, “Expanding the User Experience”), as shown in Listing 9.9. This notification will be displayed for as long as your Service is running in the foreground.

2.11

Listing 9.9: Moving a Service to the foreground

private void startPlayback(String album, String artist) {
  int NOTIFICATION_ID = 1;
 
  // Create an Intent that will open the main Activity
  // if the notification is clicked.
  Intent intent = new Intent(this, MyActivity.class);
  PendingIntent pi = PendingIntent.getActivity(this, 1, intent, 0);
 
  // Set the Notification UI parameters
  Notification notification = new Notification(R.drawable.icon,
    "Starting Playback", System.currentTimeMillis());
  notification.setLatestEventInfo(this, album, artist, pi);
 
  // Set the Notification as ongoing
  notification.flags = notification.flags |
                       Notification.FLAG_ONGOING_EVENT;
 
  // Move the Service to the Foreground
  startForeground(NOTIFICATION_ID, notification);
}

code snippet PA4AD_Ch09_MyService/src/MyMusicService.java

2.1

Moving a Service to the foreground effectively makes it impossible for the run time to kill it in order to free resources. Having multiple unkillable Services running simultaneously can make it extremely difficult for the system to recover from resource-starved situations.

Use this technique only if it is necessary in order for your Service to function properly, and even then keep the Service in the foreground only as long as absolutely necessary.

It's good practice to provide a simple way for users to disable a foreground Service—typically from whichever Activity is opened by clicking the ongoing Notification (or from the Notification itself).

When your Service no longer requires foreground priority, you can move it back to the background, and optionally remove the ongoing notification using the stopForeground method, as shown in Listing 9.10. The Notification will be canceled automatically if your Service stops or is terminated.

Listing 9.10: Moving a Service back to the background

public void pausePlayback() {
  // Move to the background and remove the Notification
  stopForeground(true);
}

code snippet PA4AD_Ch09_MyService/src/MyMusicService.java

2.1

Prior to Android 2.0 it was possible to set a Service to the foreground using the setForeground method. This method has now been deprecated and will result in a NOP (no operation performed), effectively doing nothing.

Using Background Threads

Responsiveness is one of the most critical attributes of a good Android application. To ensure that your application responds quickly to any user interaction or system event, it's vital that you move all processing and I/O operations off the main application Thread and into a child Thread.

2.1

All Android application components—including Activities, Services, and Broadcast Receivers—start on the main application Thread. As a result, time-consuming processing in any component will block all other components, including Services and the visible Activity.

In Android, Activities that don't respond to an input event (such as a key press) within 5 seconds, and Broadcast Receivers that don't complete their onReceive handlers within 10 seconds, are considered unresponsive.

Not only do you want to avoid this scenario, but you don't even want to come close. In practice, users will notice input delays and UI pauses of more than a couple of hundred milliseconds.

It's important to use background Threads for any nontrivial processing that doesn't directly interact with the user interface. It's particularly important to schedule file operations, network lookups, database transactions, and complex calculations on a background Thread.

Android offers a number of alternatives for moving your processing to the background. You can implement your own Threads and use the Handler class to synchronize with the GUI Thread before updating the UI. Alternatively, the AsyncTask class lets you define an operation to be performed in the background and provides event handlers that enable you to monitor progress and post the results on the GUI Thread.

Using AsyncTask to Run Asynchronous Tasks

The AsyncTask class implements a best practice pattern for moving your time-consuming operations onto a background Thread and synchronizing with the UI Thread for updates and when the processing is complete. It offers the convenience of event handlers synchronized with the GUI Thread to let you update Views and other UI elements to report progress or publish results when your task is complete.

AsyncTask handles all the Thread creation, management, and synchronization, enabling you to create an asynchronous task consisting of processing to be done in the background and UI updates to be performed both during the processing, and once it's complete.

AsyncTasks are a good solution for short-lived background processing whose progress and results need to be reflected on the UI. However, they aren't persisted across Activity restarts—meaning that your AsyncTask will be cancelled if the orientation of the device changes, causing the Activity to be destroyed and recreated. For longer running background processes, such as downloading data from the Internet, a Service component is a better approach.

Similarly, Cursor Loaders are better optimized for the use-case of Content Provider or database results.

Creating New Asynchronous Tasks

Each AsyncTask implementation can specify parameter types that will be used for input parameters, the progress-reporting values, and result values. If you don't need or want to take input parameters, update progress, or report a final result, simply specify Void for any or all the types required.

To create a new asynchronous task, extend the AsyncTask class, specifying the parameter types to use, as shown in the skeleton code of Listing 9.11.

2.11

Listing 9.11: AsyncTask implementation using a string parameter and result, with an integer progress value

private class MyAsyncTask extends AsyncTask<String, Integer, String> {
  @Override
  protected String doInBackground(String... parameter) {
    // Moved to a background thread.
    String result = "";
    int myProgress = 0;
    
    int inputLength = parameter[0].length();
 
    // Perform background processing task, update myProgress]
    for (int i = 1; i <= inputLength; i++) {
      myProgress = i;
      result = result + parameter[0].charAt(inputLength-i);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) { }
      publishProgress(myProgress);
    }
 
    // Return the value to be passed to onPostExecute
    return result;
  }
 
  @Override
  protected void onProgressUpdate(Integer... progress) {
    // Synchronized to UI thread.
    // Update progress bar, Notification, or other UI elements
    asyncProgress.setProgress(progress[0]);
  }
 
  @Override
  protected void onPostExecute(String result) {
    // Synchronized to UI thread.
    // Report results via UI update, Dialog, or notifications
    asyncTextView.setText(result);
  }
}

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

Your subclass should also override the following event handlers:

· doInBackground—This method will be executed on the background Thread, so place your long-running code here, and don't attempt to interact with UI objects from within this handler. It takes a set of parameters of the type defined in your class implementation.

· You can use the publishProgress method from within this handler to pass parameter values to the onProgressUpdate handler, and when your background task is complete, you can return the final result as a parameter to the onPostExecute handler, which can update the UI accordingly.

· onProgressUpdate—Override this handler to update the UI with interim progress updates. This handler receives the set of parameters passed in to publishProgress (typically from within the doInBackground handler).

· This handler is synchronized with the GUI Thread when executed, so you can safely modify UI elements.

· onPostExecute—When doInBackground has completed, the return value from that method is passed in to this event handler.

· Use this handler to update the UI when your asynchronous task has completed. This handler is synchronized with the GUI Thread when executed, so you can safely modify UI elements.

Running Asynchronous Tasks

After you've implemented an asynchronous task, execute it by creating a new instance and calling execute, as shown in Listing 9.12. You can pass in a number of parameters, each of the type specified in your implementation.

2.11

Listing 9.12: Executing an asynchronous task

String input = "redrum ... redrum";
new MyAsyncTask().execute(input); 

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

2.1

Each AsyncTask instance can be executed only once. If you attempt to call execute a second time, an exception will be thrown.

Introducing the Intent Service

The Intent Service is a convenient wrapper class that implements the best practice pattern for background Services that perform set tasks on demand, such as recurring Internet updates or data processing.

Other application components request an Intent Service complete a task by starting the Service and passing in an Intent containing the parameters necessary to complete the task.

The Intent Service queues request Intents as they are received and processes them consecutively on an asynchronous background Thread. After every received Intent has been processed, the Intent Service will terminate itself.

The Intent Service handles all the complexities around queuing multiple requests, background Thread creation, and UI Thread synchronization.

To implement a Service as an Intent Service, extend IntentService and override the onHandleIntent handler, as shown in Listing 9.13.

2.11

Listing 9.13: Implementing an Intent Service

import android.app.IntentService;
import android.content.Intent;
 
public class MyIntentService extends IntentService {
 
  public MyIntentService(String name) {
    super(name);
    // TODO Complete any required constructor tasks.
  }
 
  @Override
  public void onCreate() {
    super.onCreate();
    // TODO: Actions to perform when service is created.
  }
  
  @Override
  protected void onHandleIntent(Intent intent) {
    // This handler occurs on a background thread.
    // TODO The time consuming task should be implemented here.
    // Each Intent supplied to this IntentService will be 
    // processed consecutively here. When all incoming Intents
    // have been processed the Service will terminate itself.
  }
}

code snippet PA4AD_Ch09_MyService/src/MyIntentService.java

The onHandleIntent handler will be executed on a worker Thread, once for each Intent received. The Intent Service is the best-practice approach to creating Services that perform set tasks either on-demand or at regular intervals.

The “Using the Intent Service to Simplify the Earthquake Update Service” section demonstrates a practical example of how to use the Intent Service for recurring tasks.

Introducing Loaders

The abstract Loader class was introduced in Android 3.0 (API level 11) to encapsulate the best practice technique for asynchronous data loading within UI elements, such as Activities and Fragments. Loaders are also available within the Android Support Library.

The CursorLoader class, discussed in more detail in Chapter 8, is a concrete implementation used to asynchronously query the Content Resolver and return a Cursor.

When creating your own Loader implementation, it is typically best practice to extend the AsyncTaskLoader class rather than the Loader class directly. Although a Loader implementation is outside the scope of this book, in general your custom Loaders should:

· Asynchronously load data

· Monitor the source of the loaded data and automatically provide updated results

Manual Thread Creation and GUI Thread Synchronization

Although using Intent Services and creating AsyncTasks are useful shortcuts, there are times when you will want to create and manage your own Threads to perform background processing. This is often the case when you have long-running or inter-related Threads that require more subtle or complex management than is provided by the two techniques described so far.

In this section you learn how to create and start new Thread objects, and how to synchronize with the GUI Thread before updating the UI.

You can create and manage child Threads using Android's Handler class and the Threading classes available within java.lang.Thread. Listing 9.14 shows the simple skeleton code for moving processing onto a child Thread.

2.11

Listing 9.14: Moving processing to a background Thread

// This method is called on the main GUI thread.
private void backgroundExecution() {
  // This moves the time consuming operation to a child thread.
  Thread thread = new Thread(null, doBackgroundThreadProcessing,
                             "Background");
  thread.start();
}
 
// Runnable that executes the background processing method.
private Runnable doBackgroundThreadProcessing = new Runnable() {
  public void run() {
    backgroundThreadProcessing();
  }
};
 
// Method which does some processing in the background.
private void backgroundThreadProcessing() {
  // [ ... Time consuming operations ... ]
}

code snippet PA4AD_Ch09_MyService/src/MyService.java

Whenever you're using background Threads in a GUI environment, it's important to synchronize child Threads with the main application (GUI) Thread before attempting to create or modify UI elements.

Within your application components, Notifications and Intents are always received and handled on the GUI Thread. In all other cases, operations that explicitly interact with objects created on the GUI Thread (such as Views) or that display messages (such as Toasts) must be invoked on the main Thread.

If you are running within an Activity, you can also use the runOnUiThread method, which lets you force a method to execute on the same Thread as the Activity UI, as shown in the following code snippet:

runOnUiThread(new Runnable() {
  public void run() {
    // Update a View or other Activity UI element.
  }
});

You can also use the Handler class to post methods onto the Thread in which the Handler was created.

Using the Handler class, you can post updates to the user interface from a background Thread using the Post method. Listing 9.15 updates Listing 9.14 to demonstrate the outline for using the Handler to update the GUI Thread.

2.11

Listing 9.15: Using a Handler to synchronize with the GUI Thread

//This method is called on the main GUI thread.
private void backgroundExecution() {
  // This moves the time consuming operation to a child thread.
  Thread thread = new Thread(null, doBackgroundThreadProcessing,
                             "Background");
  thread.start();
}
 
// Runnable that executes the background processing method.
private Runnable doBackgroundThreadProcessing = new Runnable() {
  public void run() {
    backgroundThreadProcessing();
  }
};
 
// Method which does some processing in the background.
private void backgroundThreadProcessing() {
  // [ ... Time consuming operations ... ]
  
  // Use the Handler to post the doUpdateGUI
  // runnable on the main UI thread.
  handler.post(doUpdateGUI);
}
 
//Initialize a handler on the main thread.
private Handler handler = new Handler();
 
// Runnable that executes the updateGUI method.
private Runnable doUpdateGUI = new Runnable() {
  public void run() {
    updateGUI();
  }
};
 
// This method must be called on the UI thread.
private void updateGUI() {
  // [ ... Open a dialog or modify a GUI element ... ]
}

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

The Handler class also enables you to delay posts or execute them at a specific time, using the postDelayed and postAtTime methods, respectively:

// Post a method on the UI thread after 1sec.
handler.postDelayed(doUpdateGUI, 1000);
 
// Post a method on the UI thread after the device has been in
// use for 5mins. 
int upTime = 1000*60*5;
handler.postAtTime(doUpdateGUI, SystemClock.uptimeMillis()+upTime);

Using Alarms

Alarms are a means of firing Intents at predetermined times or intervals. Unlike Timers, Alarms operate outside the scope of your application, so you can use them to trigger application events or actions even after your application has been closed. Alarms are particularly powerful when used in combination with Broadcast Receivers, enabling you to set Alarms that fire broadcast Intents, start Services, or even open Activities, without your application needing to be open or running.

Alarms are an effective means to reducing your application's resource requirements, by enabling you to stop Services and eliminate timers while maintaining the ability to perform scheduled actions. You can use Alarms to schedule regular updates based on network lookups, to schedule time-consuming or cost-bound operations at “off-peak” times, or to schedule retries for failed operations.

2.1

For timing operations that occur only during the lifetime of your applications, using the Handler class in combination with Timers and Threads is a better approach than using Alarms, as this allows Android better control over system resources. Alarms provide a mechanism to reduce the lifetime of your applications by moving scheduled events out of their control

Alarms in Android remain active while the device is in sleep mode and can optionally be set to wake the device; however, all Alarms are canceled whenever the device is rebooted.

Alarm operations are handled through the AlarmManager, a system Service accessed via getSystemService, as follows:

AlarmManager alarmManager = 
 (AlarmManager)getSystemService(Context.ALARM_SERVICE);

Creating, Setting, and Canceling Alarms

To create a new one-shot Alarm, use the set method and specify an alarm type, a trigger time, and a Pending Intent to fire when the Alarm triggers. If the trigger time you specify for the Alarm occurs in the past, the Alarm will be triggered immediately.

The following four alarm types are available:

· RTC_WAKEUP—Wakes the device from sleep to fire the Pending Intent at the clock time specified.

· RTC—Fires the Pending Intent at the time specified but does not wake the device.

· ELAPSED_REALTIME—Fires the Pending Intent based on the amount of time elapsed since the device was booted but does not wake the device. The elapsed time includes any period of time the device was asleep.

· ELAPSED_REALTIME_WAKEUP—Wakes the device from sleep and fires the Pending Intent after a specified length of time has passed since device boot.

Your selection will determine if the time value passed in the set method represents a specific time or an elapsed wait.

Listing 9.16 shows the Alarm-creation process.

2.11

Listing 9.16: Creating a waking Alarm that triggers in 10 seconds

// Get a reference to the Alarm Manager
AlarmManager alarmManager = 
 (AlarmManager)getSystemService(Context.ALARM_SERVICE);
 
// Set the alarm to wake the device if sleeping.
int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
 
// Trigger the device in 10 seconds.
long timeOrLengthofWait = 10000;
 
// Create a Pending Intent that will broadcast and action
String ALARM_ACTION = "ALARM_ACTION";
Intent intentToFire = new Intent(ALARM_ACTION);
PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0,
  intentToFire, 0);
 
// Set the alarm
alarmManager.set(alarmType, timeOrLengthofWait, alarmIntent);

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

When the Alarm goes off, the Pending Intent you specified will be broadcast. Setting a second Alarm using the same Pending Intent replaces the preexisting Alarm.

To cancel an Alarm, call cancel on the Alarm Manager, passing in the Pending Intent you no longer want to trigger, as shown in the Listing 9.17.

2.11

Listing 9.17: Canceling an Alarm

alarmManager.cancel(alarmIntent);

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

Setting Repeating Alarms

Repeating alarms work in the same way as the one-shot alarms but will trigger repeatedly at the specified interval.

Because alarms are set outside your Application lifecycle, they are perfect for scheduling regular updates or data lookups so that they don't require a Service to be constantly running in the background.

To set a repeating alarm, use the setRepeating or setInexactRepeating method on the Alarm Manager. Both methods support an alarm type, an initial trigger time, and a Pending Intent to fire when the alarm triggers (as described in the previous section).

Use setRepeating when you need fine-grained control over the exact interval of your repeating alarm. The interval value passed in to this method lets you specify an exact interval for your alarm, down to the millisecond.

The setInexactRepeating method helps to reduce the battery drain associated with waking the device on a regular schedule to perform updates. At run time Android will synchronize multiple inexact repeating alarms and trigger them simultaneously.

Rather than specifying an exact interval, the setInexactRepeating method accepts one of the following Alarm Manager constants:

· INTERVAL_FIFTEEN_MINUTES

· INTERVAL_HALF_HOUR

· INTERVAL_HOUR

· INTERVAL_HALF_DAY

· INTERVAL_DAY

Using an inexact repeating alarm, as shown in Listing 9.18, prevents each application from separately waking the device in a similar but nonoverlapping period. By synchronizing these alarms, the system is able to limit the impact of regularly repeating events on battery resources.

2.11

Listing 9.18: Setting an inexact repeating alarm

// Get a reference to the Alarm Manager
AlarmManager alarmManager = 
 (AlarmManager)getSystemService(Context.ALARM_SERVICE);
 
// Set the alarm to wake the device if sleeping.
int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
 
// Schedule the alarm to repeat every half hour.
long timeOrLengthofWait = AlarmManager.INTERVAL_HALF_HOUR;
 
// Create a Pending Intent that will broadcast and action
String ALARM_ACTION = "ALARM_ACTION";
Intent intentToFire = new Intent(ALARM_ACTION);
PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0,
  intentToFire, 0);
 
// Wake up the device to fire an alarm in half an hour, and every
// half-hour after that.
alarmManager.setInexactRepeating(alarmType,
                           timeOrLengthofWait,
                           timeOrLengthofWait,
                           alarmIntent);

code snippet PA4AD_Ch09_MyService/src/MyActivity.java

2.1

The battery impact of setting regularly repeating alarms can be significant. It is good practice to limit your alarm frequency to the slowest acceptable rate, wake the device only if necessary, and use an inexact repeating alarm whenever possible.

Repeating Alarms are canceled in the same way as one-shot Alarms, by calling cancel on the Alarm Manager and passing in the Pending Intent you no longer want to trigger.

Using Repeating Alarms to Schedule Network Refreshes

In this modification to the Earthquake example, you use Alarms to replace the Timer currently used to schedule Earthquake network refreshes.

One of the most significant advantages of this approach is that it allows the Service to stop itself when it has completed a refresh, freeing significant system resources.

1. Start by creating a new EarthquakeAlarmReceiver class that extends BroadcastReceiver:

package com.paad.earthquake;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
 
public class EarthquakeAlarmReceiver extends BroadcastReceiver {
 
  @Override
  public void onReceive(Context context, Intent intent) {
  }
 
}

2. Override the onReceive method to explicitly start the EarthquakeUpdateService:

@Override
public void onReceive(Context context, Intent intent) {
  Intent startIntent = new Intent(context, EarthquakeUpdateService.class);
  context.startService(startIntent);
}

3. Create a new public static String to define the action that will be used to trigger this Broadcast Receiver:

public static final String ACTION_REFRESH_EARTHQUAKE_ALARM = 
  "com.paad.earthquake.ACTION_REFRESH_EARTHQUAKE_ALARM";

4. Add the new EarthquakeAlarmReceiver to the manifest, including an intent-filter tag that listens for the action defined in step 3:

<receiver android:name=".EarthquakeAlarmReceiver">
  <intent-filter>
    <action
      android:name="com.paad.earthquake.ACTION_REFRESH_EARTHQUAKE_ALARM"
    />
  </intent-filter>
</receiver>

5. Within the Earthquake Update Service, override the onCreate method to get a reference to the AlarmManager, and create a new PendingIntent that will be fired when the Alarm is triggered. You can also remove the timerTask initialization.

private AlarmManager alarmManager;
private PendingIntent alarmIntent;
 
@Override
public void onCreate() {
  super.onCreate();
  alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
 
  String ALARM_ACTION =
    EarthquakeAlarmReceiver.ACTION_REFRESH_EARTHQUAKE_ALARM;
  Intent intentToFire = new Intent(ALARM_ACTION);
  alarmIntent =
    PendingIntent.getBroadcast(this, 0, intentToFire, 0);
}

6. Modify the onStartCommand handler to set an inexact repeating Alarm rather than use a Timer to schedule the refreshes (if automated updates are enabled). Setting a new Intent with the same action automatically cancels any previous Alarms. Take this opportunity to modify the return result. Rather than setting the Service to sticky, return Service.START_NOT_STICKY. In step 7 you will stop the Service when the background refresh is complete; the use of alarms guarantees that another refresh will occur at the specified update frequency, so there's no need for the system to restart the Service if it is killed mid-refresh.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  // Retrieve the shared preferences
  Context context = getApplicationContext();
  SharedPreferences prefs = 
    PreferenceManager.getDefaultSharedPreferences(context);
 
  int updateFreq = 
    Integer.parseInt(prefs.getString(PreferencesActivity.PREF_UPDATE_FREQ, "60"));
  boolean autoUpdateChecked = 
    prefs.getBoolean(PreferencesActivity.PREF_AUTO_UPDATE, false);
 
  if (autoUpdateChecked) {
    int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
    long timeToRefresh = SystemClock.elapsedRealtime() +
                         updateFreq*60*1000;
    alarmManager.setInexactRepeating(alarmType, timeToRefresh,
                                     updateFreq*60*1000, alarmIntent);
  }
  else
    alarmManager.cancel(alarmIntent);
 
  Thread t = new Thread(new Runnable() {
    public void run() {
      refreshEarthquakes(); 
    }
  });
  t.start();
 
  return Service.START_NOT_STICKY;
};

7. Within the refreshEarthquakes method, update the last try-finally case to call stopSelf when the background refresh has completed:

private void refreshEarthquakes() {
  [... existing refreshEarthquakes method ...]
  finally {
    stopSelf();
  } 
}

8. Remove the updateTimer instance variable and the Timer Task instance doRefresh.

When you run the updated application, the behavior should appear identical to the previous iteration of the application. Behind the scenes, however, the Service is being terminated when each update is complete, reducing the application's memory footprint and improving overall performance.

In the next section you'll use the Intent Service to further simplify and optimize this Service component.

2.1

All code snippets in this example are part of the Chapter 9 Earthquake Part 2 project, available for download at www.wrox.com.

Using the Intent Service to Simplify the Earthquake Update Service

The following example shows how to further simplify the EarthquakeUpdateService using an Intent Service.

1. Modify the inheritance of the Earthquake Update Service so that it extends IntentService:

public class EarthquakeUpdateService extends IntentService {

2. Create a new constructor that passes the name parameter to the super class:

public EarthquakeUpdateService() {
  super("EarthquakeUpdateService");
}
 
public EarthquakeUpdateService(String name) {
  super(name);
}

3. Override the onHandleIntent handler, moving all the code currently within onStartCommand into this handler. Note that you don't have to explicitly create a background Thread to execute the refresh; the Intent Service base class will do this for you.

@Override
protected void onHandleIntent(Intent intent) {
  // Retrieve the shared preferences
  Context context = getApplicationContext();
  SharedPreferences prefs =
    PreferenceManager.getDefaultSharedPreferences(context);
 
  int updateFreq =
    Integer.parseInt(prefs.getString(PreferencesActivity.PREF_UPDATE_FREQ, "60"));
  boolean autoUpdateChecked =
    prefs.getBoolean(PreferencesActivity.PREF_AUTO_UPDATE, false);
 
  if (autoUpdateChecked) {
    int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
    long timeToRefresh = SystemClock.elapsedRealtime() +
                         updateFreq*60*1000;
    alarmManager.setInexactRepeating(alarmType, timeToRefresh,
                                     updateFreq*60*1000, alarmIntent);
  }
  else
    alarmManager.cancel(alarmIntent);
 
  refreshEarthquakes();
}

The Intent Service implementation will queue Intents as they are received and process them consecutively, so there's no need to check for stacking refresh requests. After every received Intent has been processed, the Intent Service will terminate itself.

4. Remove the now empty onStartCommand handler, and remove the call to stopSelf you added to the finally clause of the refreshEarthquakes method step 7 of the previous example.

private void refreshEarthquakes() {
  [... existing refreshEarthquakes method ...]
  finally {
  }
}

2.1

All code snippets in this example are part of the Chapter 9 Earthquake 3 project,

available for download at www.wrox.com.