Chapter 20

Dealing with Threads

Users like snappy applications. Users do not like applications that feel sluggish. The way to help make your application feel snappy to users is to use the standard threading capabilities built into Android. This chapter will walk you through the issues involved with thread management in Android and some of the options for keeping the UI crisp and responsive.

The Main Application Thread

You might think that when you call setText() on a TextView, the screen is updated with the text you supply, right then and there. That is not how it works. Rather, everything that modifies the widget-based UI goes through a message queue. Calls to setText() do not update the screen; they just pop a message on a queue telling the operating system to update the screen. The operating system pops these messages off of this queue and does what the messages require.

The queue is processed by one thread, variously called the main application thread and the UI thread. As long as that thread can keep processing messages, the screen will update, user input will be handled, and so on.

However, the main application thread is also used for nearly all callbacks into your activity. Your onCreate(), onClick(), onListItemClick(), and similar methods are all called on the main application thread. While your code is executing in these methods, Android is not processing messages on the queue, meaning the screen does not update, user input is not handled, and so on.

This, of course, is bad. So bad, in fact, that if you take more than a few seconds to do work on the main application thread, Android may display the dreaded “application not responding” (ANR) error, and your activity may be killed off. Hence, you want to make sure that all of your work on the main application thread happens quickly. This means that anything slow should be done in a background thread, so as not to tie up the main application thread. This includes activities such as the following:

  • Internet access, such as sending data to a web service or downloading an image
  • Significant file operations, since flash storage can be remarkably slow at times
  • Any sort of complex calculations

Fortunately, Android supports threads using the standard Thread class from Java, plus all the wrappers and control structures you would expect, such as the java.util.concurrent class package.

However, there is one big limitation: you cannot modify the UI from a background thread. You can modify the UI only from the main application thread. Hence, you need to move long-running work into background threads, but those threads need to do something to arrange to update the UI using the main application thread. Android provides a wide range of tools to do just that, and these tools are the primary focus of this chapter.

Making Progress with ProgressBars

If you are going to fork background threads to do work on behalf of the user, you should consider keeping the user informed that work is going on. This is particularly true if the user is effectively waiting for that background work to complete.

The typical approach to keeping users informed of progress is some form of progress bar, like you see when you copy a bunch of files from place to place in many desktop operating systems. Android supports this through the ProgressBar widget.

A ProgressBar keeps track of progress, defined as an integer, with 0 indicating no progress has been made. You can define the maximum end of the range—which value indicates progress is complete—via setMax(). By default, a ProgressBar starts with a progress of 0, though you can start from some other position via setProgress(). If you prefer your progress bar to be indeterminate, use setIndeterminate() and set it to true.

In your Java code, you can either positively set the amount of progress that has been made (via setProgress()) or increment the progress from its current amount (via incrementProgressBy()). You can find out how much progress has been made via getProgress().

There are other alternatives for displaying progress—ProgressDialog, a progress indicator in the activity's title bar, and so on—but a ProgressBar is a good place to start.

Getting Through the Handlers

The most flexible means of making an Android-friendly background thread is to create an instance of a Handler subclass. You need only one Handler object per activity, and you do not need to manually register it. Merely creating the instance is sufficient to register it with the Android threading subsystem.

Your background thread can communicate with the Handler, which will do all of its work on the activity's UI thread. This is important, as UI changes, such as updating widgets, should occur only on the activity's UI thread.

You have two options for communicating with the Handler: messages and Runnable objects.

Messages

To send a Message to a Handler, first invoke obtainMessage() to get the Message object out of the pool. There are a few flavors of obtainMessage(), allowing you to create empty Message objects or ones populated with message identifiers and arguments. The more complicated your Handler processing needs to be, the more likely it is you will need to put data into the Message to help the Handler distinguish different events.

Then, you send the Message to the Handler via its message queue, using one of the sendMessage...() family of methods, such as the following:

  • sendMessage(): Puts the message on the queue immediately
  • sendMessageAtFrontOfQueue(): Puts the message on the queue immediately and places it at the front of the message queue (versus the back, which is the default), so your message takes priority over all others
  • sendMessageAtTime(): Puts the message on the queue at the stated time, expressed in the form of milliseconds based on system uptime (SystemClock.uptimeMillis())
  • sendMessageDelayed(): Puts the message on the queue after a delay, expressed in milliseconds
  • sendEmptyMessage(): Sends an empty Message object to the queue, allowing you to skip the obtainMessage() step if you were planning on leaving it empty anyway

To process these messages, your Handler needs to implement handleMessage(), which will be called with each message that appears on the message queue. There, the Handler can update the UI as needed. However, it should still do that work quickly, as other UI work is suspended until the Handler is finished.

For example, let's create a ProgressBar and update it via a Handler. Here is the layout from the Threads/Handler sample project:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
  <ProgressBar android:id="@+id/progress"
    style="?android:attr/progressBarStyleHorizontal"

    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
</LinearLayout>

The ProgressBar, in addition to setting the width and height as normal, also employs the style property. This particular style indicates the ProgressBar should be drawn as the traditional horizontal bar showing the amount of work that has been completed.

And here is the Java:

package com.commonsware.android.threads;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;
import java.util.concurrent.atomic.AtomicBoolean;

public class HandlerDemo extends Activity {
  ProgressBar bar;
  Handler handler=new Handler() {
    @Override
    public void handleMessage(Message msg) {
      bar.incrementProgressBy(5);
    }
  };
  AtomicBoolean isRunning=new AtomicBoolean(false);

  @Override
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);
    bar=(ProgressBar)findViewById(R.id.progress);
  }

  public void onStart() {
    super.onStart();
    bar.setProgress(0);

    Thread background=new Thread(new Runnable() {
      public void run() {
        try {
          for (int i=0;i<20 && isRunning.get();i++) {
            Thread.sleep(1000);
            handler.sendMessage(handler.obtainMessage());
          }
        }
        catch (Throwable t) {
          // just end the background thread
        }
      }
    });

    isRunning.set(true);
    background.start();

  }

  public void onStop() {
    super.onStop();
    isRunning.set(false);
  }
}

As part of constructing the Activity, we create an instance of Handler, with our implementation of handleMessage(). Basically, for any message received, we update the ProgressBar by 5 points, and then exit the message handler.

We then take advantage of onStart() and onStop(). In onStart(), we set up a background thread. In a real system, this thread would do something meaningful. Here, we just sleep 1 second, post a Message to the Handler, and repeat for a total of 20 passes. This, combined with the 5-point increase in the ProgressBar position, will march the bar clear across the screen, as the default maximum value for ProgressBar is 100. You can adjust that maximum via setMax(). For example, you might set the maximum to be the number of database rows you are processing, and update once per row.

Note that we then leave onStart(). This is crucial. The onStart() method is invoked on the activity UI thread, so it can update widgets and such. However, that means we need to get out of onStart(), both to let the Handler get its work done and to inform Android that our activity is not stuck.

The resulting activity is simply a horizontal progress bar, as shown in Figure 20–1.

images

Figure 20–1. The HandlerDemo sample application

Note, though, that while ProgressBar samples like this one show your code arranging to update the progress on the UI thread, for this specific widget, that is not necessary. At least as of Android 1.5, ProgressBar is now UI thread safe, in that you can update it from any thread, and it will handle the details of performing the actual UI update on the UI thread.

Runnables

If you would rather not fuss with Message objects, you can also pass Runnable objects to the Handler, which will run those Runnable objects on the activity UI thread. Handler offers a set of post...() methods for passing Runnable objects in for eventual processing.

Just as Handler supports post() and postDelayed() to add Runnable objects to the event queue, you can use those same methods on any View (i.e., any widget or container). This slightly simplifies your code, in that you can then skip the Handler object.

Where Oh Where Has My UI Thread Gone?

Sometimes, you may not know if you are currently executing on the UI thread of your application. For example, if you package some of your code in a JAR for others to reuse, you might not know whether your code is being executed on the UI thread or from a background thread.

To help combat this problem, Activity offers runOnUiThread(). This works similarly to the post() methods on Handler and View, in that it queues up a Runnable to run on the UI thread, if you are not on the UI thread right now. If you already are on the UI thread, it invokes the Runnable immediately. This gives you the best of both worlds: no delay if you are on the UI thread, yet safety in case you are not.

Asyncing Feeling

Android 1.5 introduced a new way of thinking about background operations: AsyncTask. In one (reasonably) convenient class, Android handles all of the chores of doing work on the UI thread versus on a background thread. Moreover, Android itself allocates and removes that background thread. And, it maintains a small work queue, further accentuating the fire-and-forget feel to AsyncTask.

The Theory

There is a saying, popular in marketing circles, “When a man buys a 1/4-inch drill bit at a hardware store, he does not want a 1/4-inch drill bit—he wants 1/4-inch holes.” Hardware stores cannot sell holes, so they sell the next-best thing: devices (drills and drill bits) that make creating holes easy.

Similarly, Android developers who have struggled with background thread management do not strictly want background threads. Rather, they want work to be done off the UI thread, so users are not stuck waiting and activities do not get the dreaded ANR error. And while Android cannot magically cause work to not consume UI thread time, it can offer things that make such background operations easier and more transparent. AsyncTask is one such example.

To use AsyncTask, you must do the following:

  • Create a subclass of AsyncTask, commonly as a private inner class of something that uses the task (e.g., an activity)
  • Override one or more AsyncTask methods to accomplish the background work, plus whatever work associated with the task that needs to be done on the UI thread (e.g., update progress)
  • When needed, create an instance of the AsyncTask subclass and call execute() to have it begin doing its work

What you do not have to do is

  • Create your own background thread
  • Terminate that background thread at an appropriate time
  • Call all sorts of methods to arrange for bits of processing to be done on the UI thread

AsyncTask, Generics, and Varargs

Creating a subclass of AsyncTask is not quite as easy as, say, implementing the Runnable interface. AsyncTask uses generics, and so you need to specify three data types:

  • The type of information that is needed to process the task (e.g., URLs to download)
  • The type of information that is passed within the task to indicate progress
  • The type of information that is passed to the post-task code when the task is completed

What makes this all the more confusing is that the first two data types are actually used as varargs, meaning that an array of these types is used within your AsyncTask subclass.

This should become clearer as we work our way toward an example.

The Stages of AsyncTask

There are four methods you can override in AsyncTask to accomplish your ends.

The one you must override, for the task class to be useful, is doInBackground(). This will be called by AsyncTask on a background thread. It can run as long as is necessary to accomplish whatever work needs to be done for this specific task. Note, though, that tasks are meant to be finite; using AsyncTask for an infinite loop is not recommended.

The doInBackground() method will receive, as parameters, a varargs array of the first of the three data types listed in the preceding section—the data needed to process the task. So, if your task's mission is to download a collection of URLs, doInBackground() will receive those URLs to process. The doInBackground() method must return a value of the third data type listed in the preceding section—the result of the background work.

You may wish to override onPreExecute(). This method is called, from the UI thread, before the background thread executes doInBackground(). Here, you might initialize a ProgressBar or otherwise indicate that background work is commencing.

Also, you may wish to override onPostExecute(). This method is called, from the UI thread, after doInBackground() completes. It receives, as a parameter, the value returned by doInBackground() (e.g., success or failure flag). Here, you might dismiss the ProgressBar and make use of the work done in the background, such as updating the contents of a list.

In addition, you may wish to override onProgressUpdate(). If doInBackground() calls the task's publishProgress() method, the object(s) passed to that method are provided to onProgressUpdate(), but in the UI thread. That way, onProgressUpdate() can alert the user as to the progress that has been made on the background work, such as updating a ProgressBar or continuing an animation. The onProgressUpdate() method will receive a varargs of the second data type from the preceding list—the data published by doInBackground() via publishProgress().

A Sample Task

As mentioned earlier, implementing an AsyncTask is not quite as easy as implementing a Runnable. However, once you get past the generics and varargs, it is not too bad.

For example, the following is an implementation of a ListActivity that uses an AsyncTask, from the Threads/Asyncer sample project:

package com.commonsware.android.async;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;


public class AsyncDemo extends ListActivity {
  private static final String[] items={"lorem", "ipsum", "dolor",
                                     "sit", "amet", "consectetuer",
                                     "adipiscing", "elit", "morbi",
                                     "vel", "ligula", "vitae",
                                     "arcu", "aliquet", "mollis",
                                     "etiam", "vel", "erat",
                                     "placerat", "ante",
                                     "porttitor", "sodales",
                                     "pellentesque", "augue",
                                     "purus"};
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    setListAdapter(new ArrayAdapter<String>(this,
                       android.R.layout.simple_list_item_1,
                       new ArrayList()));

    new AddStringTask().execute();
  }

  class AddStringTask extends AsyncTask<Void, String, Void> {
    @Override
    protected Void doInBackground(Void... unused) {
      for (String item : items) {
        publishProgress(item);
        SystemClock.sleep(200);
      }

      return(null);
    }

    @Override
    protected void onProgressUpdate(String... item) {
      ((ArrayAdapter)getListAdapter()).add(item[0]);
    }

    @Override
    protected void onPostExecute(Void unused) {
      Toast
        .makeText(AsyncDemo.this, "Done!", Toast.LENGTH_SHORT)
        .show();
    }
  }
}

This is another variation on the lorem ipsum list of words, used frequently throughout this book. This time, rather than simply hand the list of words to an ArrayAdapter, we simulate having to work to create these words in the background using AddStringTask, our AsyncTask implementation.

Let's examine this project's code piece by piece.

The AddStringTask Declaration

The AddStringTask declaration is as follows:

class AddStringTask extends AsyncTask<Void, String, Void> {

Here, we use the generics to set up the specific types of data we are going to leverage in AddStringTask:

  • We do not need any configuration information in this case, so our first type is Void.
  • We want to pass each string generated by our background task to onProgressUpdate(), to allow us to add it to our list, so our second type is String.
  • We do not have any results, strictly speaking (beyond the updates), so our third type is Void.
The doInBackground() Method

The doInBackground() method is next in the code:

@Override
protected Void doInBackground(Void... unused) {
  for (String item : items) {
    publishProgress(item);
    SystemClock.sleep(200);
  }

  return(null);
}

The doInBackground() method is invoked in a background thread. Hence, we can take as long as we like. In a production application, we might be doing something like iterating over a list of URLs and downloading each. Here, we iterate over our static list of lorem ipsum words, call publishProgress() for each, and then sleep 200 milliseconds to simulate real work being done.

Since we elected to have no configuration information, we should not need parameters to doInBackground(). However, the contract with AsyncTask says we must accept a varargs of the first data type, which is why our method parameter is Void... unused.

Since we elected to have no results, we should not need to return anything. Again, though, the contract with AsyncTask says we must return an object of the third data type. Since that data type is Void, our returned object is null.

The onProgressUpdate() Method

Next up is the onProgressUpdate() method:

@Override
protected void onProgressUpdate(String... item) {
  ((ArrayAdapter)getListAdapter()).add(item[0]);
}

The onProgressUpdate() method is called on the UI thread, and we want to do something to let the user know we are making progress on loading these strings. In this case, we simply add the string to the ArrayAdapter, so it is appended to the end of the list.

The onProgressUpdate() method receives a String... varargs because that is the second data type in our class declaration. Since we are passing only one string per call to publishProgress(), we need to examine only the first entry in the varargs array.

The onPostExecute() Method

The next method is onPostExecute():

@Override
protected void onPostExecute(Void unused) {
  Toast
    .makeText(AsyncDemo.this, "Done!", Toast.LENGTH_SHORT)
    .show();
}

The onPostExecute() method is called on the UI thread, and we want to do something to indicate that the background work is complete. In a real system, there may be some ProgressBar to dismiss or some animation to stop. Here, we simply raise a Toast.

Since we elected to have no results, we should not need any parameters. The contract with AsyncTask says we must accept a single value of the third data type. Since that data type is Void, our method parameter is Void unused.

The Activity

The activity is as follows:

new AddStringTask().execute();

To use AddStringTask, we simply create an instance and call execute() on it. That starts the chain of events eventually leading to the background thread doing its work.

If AddStringTask required configuration parameters, we would have not used Void as our first data type, and the constructor would accept zero or more parameters of the defined type. Those values would eventually be passed to doInBackground().

The Results

If you build, install, and run this project, you will see the list being populated in real time over a few seconds, followed by a Toast indicating completion, as shown in Figure 20–2.

images

Figure 20–2. The AsyncDemo, partway through loading the list of words

Threads and Rotation

One problem with the default destroy-and-create cycle that activities go through on an orientation change comes from background threads. If the activity has started some background work—through an AsyncTask, for example—and then the activity is destroyed and re-created, the AsyncTask needs to know about this somehow. Otherwise, the AsyncTask might well send updates and final results to the old activity, with the new activity none the wiser. In fact, the new activity might start the background work again, wasting resources.

One way to deal with this is to disable the destroy-and-create cycle, by taking over configuration changes, as described in a previous section. Another alternative is to have a smarter activity and AsyncTask. You can see an example of that in the Rotation/RotationAsync sample project. As shown next, this project uses a ProgressBar, much like the Handler demo from earlier in this chapter. It also has a TextView to indicate when the background work is completed, initially invisible.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"

    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <ProgressBar android:id="@+id/progress"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
  />
  <TextView android:id="@+id/completed"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Work completed!"
      android:visibility="invisible"
  />
</LinearLayout>

The “business logic” is for an AsyncTask to do some (fake) work in the background, updating the ProgressBar along the way, and making the TextView visible when it is finished. More importantly, it needs to do this in such a way as to behave properly if the screen is rotated. This means the following:

  • We cannot “lose” our AsyncTask, having it continue doing work and updating the wrong activity.
  • We cannot start a second AsyncTask, thereby doubling our workload.
  • We need to have the UI correctly reflect our work's progress or completion.

Manual Activity Association

Earlier, this chapter showed the use of an AsyncTask that was implemented as a regular inner class of the Activity class. That works well when you are not concerned about rotation. For example, if the AsyncTask is not affecting the UI—such as uploading a photo—rotation will not be an issue for you. Having the AsyncTask as an inner class of the Activity means you get ready access to the Activity for any place where you need a Context.

However, for the rotation scenario, a regular inner class will work poorly. The AsyncTask will think it knows which Activity it is supposed to work with, but in reality it will be holding onto an implicit reference to the old activity, not one after an orientation change.

So, in RotationAsync, the RotationAwareTask class is a static inner class. This means RotationAwareTask does not have any implicit reference to any RotationAsyncActivity (old or new):

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;


public class RotationAsync extends Activity {
  private ProgressBar bar=null;
  private RotationAwareTask task=null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    bar=(ProgressBar)findViewById(R.id.progress);

    task=(RotationAwareTask)getLastNonConfigurationInstance();

    if (task==null) {
      task=new RotationAwareTask(this);
      task.execute();
    }
    else {
      task.attach(this);
      updateProgress(task.getProgress());

      if (task.getProgress()>=100) {
        markAsDone();
      }
    }
  }

  @Override
  public Object onRetainNonConfigurationInstance() {
    task.detach();

    return(task);
  }

  void updateProgress(int progress) {
    bar.setProgress(progress);
  }

  void markAsDone() {
    findViewById(R.id.completed).setVisibility(View.VISIBLE);
  }

  static class RotationAwareTask extends AsyncTask<Void, Void, Void> {
    RotationAsync activity=null;
    int progress=0;

    RotationAwareTask(RotationAsync activity) {
      attach(activity);
    }

    @Override
    protected Void doInBackground(Void... unused) {
      for (int i=0;i<20;i++) {
        SystemClock.sleep(500);
        publishProgress();
      }


      return(null);
    }

    @Override
    protected void onProgressUpdate(Void... unused) {
      if (activity==null) {
        Log.w("RotationAsync", "onProgressUpdate() skipped – no activity");
      }
      else {
        progress+=5;
        activity.updateProgress(progress);
      }
    }

    @Override
    protected void onPostExecute(Void unused) {
      if (activity==null) {
        Log.w("RotationAsync", "onPostExecute() skipped – no activity");
      }
      else {
        activity.markAsDone();
      }
    }

    void detach() {
      activity=null;
    }

    void attach(RotationAsync activity) {
      this.activity=activity;
    }

    int getProgress() {
      return(progress);
    }
  }
}

Since we want RotationAwareTask to update the current RotationAsyncActivity, we supply that Activity when we create the task, via the constructor. RotationAwareTask also has attach() and detach() methods to change which Activity the task knows about, as we will see shortly.

Flow of Events

When RotationAsync starts up for the first time, it creates a new instance of the RotationAwareTask class and executes it. At this point, the task has a reference to the RotationAsyncActivity and can do its (fake) work, telling RotationAsync to update the progress along the way.

Now, suppose that during the middle of the doInBackground() processing, the user rotates the screen. Our Activity will be called with onRetainNonConfigurationInstance(). Here, we want to do two things:

  • Since this Activity instance is being destroyed, we need to make sure the task no longer holds onto a reference to it. Hence, we call detach(), causing the task to set its RotationAsync data member (activity) to null.
  • We return the RotationAwareTask object, so that our new RotationAsync instance can get access to it.

Eventually, the new RotationAsync instance will be created. In onCreate(), we try to get access to any current RotationAwareTask instance via getLastNonConfigurationInstance(). If that was null, then we know that this is a newly created activity, and so we create a new task. If, however, getLastNonConfigurationInstance() returned the task object from the old RotationAsync instance, we hold onto it and update our UI to reflect the current progress that has been made. We also attach() the new RotationAsync to the RotationAwareTask, so as further progress is made, the task can notify the proper activity.

The net result is that our ProgressBar smoothly progresses from 0 to 100, even while rotations are going on.

Why This Works

Most callback methods in Android are driven by messages on the message queue being processed by the main application thread. Normally, this queue is being processed whenever the main application thread is not otherwise busy, such as running our code. However, when a configuration change occurs, like a screen rotation, that no longer holds true. In between the call to the onRetainNonConfigurationInstance() instance of the old activity and the completion of onCreate() of the new activity, the message queue is left alone.

So, let's suppose that, in between onRetainNonConfigurationInstance() activity and the subsequent onCreate(), our AsyncTask's background work completes. This will trigger onPostExecute() to be called...eventually. However, since onPostExecute() is actually launched from a message on the message queue, onPostExecute() will not be called until after our onCreate() has completed. Hence, our AsyncTask can keep running during the configuration change, as long as we do two things:

  • In onCreate() of the new activity instance, we update the AsyncTask to have it work with our new activity, rather than the old one.
  • We do not attempt to use the activity from doInBackground().

And Now, the Caveats

Background threads, while eminently possible using the Android Handler system, are not all happiness and warm puppies. Background threads not only add complexity, but also have real-world costs in terms of available memory, CPU, and battery life. Hence, you need to account for a wide range of scenarios with your background thread, including the following:

  • The possibility that users will interact with your activity's UI while the background thread is chugging along. If the work that the background thread is doing is altered or invalidated by the user input, you will need to communicate this to the background thread. Android includes many classes in the java.util.concurrent package that will help you communicate safely with your background thread.
  • The possibility that the activity will be killed off while background work is going on. For example, after starting your activity, the user might have a call come in, followed by a text message, followed by a need to look up a contact—all of which might be sufficient to kick your activity out of memory. Chapter 18 covers the various events Android will take your activity through; hook to the proper ones, and be sure to shut down your background thread cleanly when you have the chance.
  • The possibility that users will get irritated if you chew up a lot of CPU time and battery life without giving any payback. Tactically, this means using ProgressBar or other means of letting users know that something is happening. Strategically, this means you still need to be efficient at what you do—background threads are no panacea for sluggish or pointless code.
  • The possibility that you will encounter an error during background processing. For example, if you are gathering information from the Internet, the device might lose connectivity. Alerting the user of the problem via a notification (covered in Chapter 37) and shutting down the background thread may be your best option.
..................Content has been hidden....................

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