teaching machines

CS 491 Lecture 14 – Wevents Part III

October 25, 2011 by . Filed under cs491 mobile, fall 2011, lectures.

Agenda

AsyncTask

In these days of big data, complex computation, and networks, we have need to perform background tasks whose results affect the UI. As UI stuff can only be done on the UI thread, one must take care to use a handler. The idea is this:

private Handler handler = new Handler();
...
new Thread(new Runnable() {
  public void run() {
    // do background task
    handler.post(new Runnable() {
      // update UI
    });
  }
).start();

This need is so common and writing concurrency code is so prone to developer messup that Android ships with an abstraction of this task called AsyncTask.

private class MyBackgroundTask extends AsyncTask<Void, Integer, Bundle> {
  @Override
  protected Bundle doInBackground(Void... args) {
    Bundle aBundle = new Bundle();
    // do background task A
    publishProgress(50);
    // do background task B
    return aBundle;
  }
    
  protected void onProgressUpdate(Integer... progress) {
    // update UI to show progress
  }
    
  protected void onPostExecute(Bundle result) {
    // update UI with result
  }
}

AsyncTask vs. IntentService + ResultReceiver

In Wevents, we solved the problem of background tasks hooking up changes to the UI by performing the background task in an IntentService and then invoking a callback on the ResultReceiver, which ultimately was the Activity that needed its UI updated. Now we see that AsyncTask can offer us the same thing. Which path do we choose?

AsyncTasks are strongly tied to their host Activity. If that Activity goes away, so will our AsyncTask. But isn’t that okay? It’s results were only going to affect the UI. If the Activity dies, the AsyncTask won’t have anything to update.

A Service has a higher priority than a backgrounded Activity. Its more likely to get results. But what advantage is this if the Activity/ResultReceiver it needs to push an update to has been stopped? If all we have is a UI update, there is no advantage. But if we want to do more with the results, like cache them locally for when the device goes offline, a Service is the way to go. In Wevents, we’re not doing that right now, so we could have used an AsyncTask.

Handling configuration changes

A problem we hinted at last time is that our Activity may issue a request to the service but get destroyed and recreated on configuration changes before the result comes down the pipe. Let’s simulate this. We can add a sleep call to the fetch thread and then change the configuration.

The IntentService is hanging on to a reference to the ResultReceiver, which is hanging on to a reference to the Activity. That’s bad for two reasons: 1) the garbage collector can’t release the Activity resources and 2) when the result does come down the pipe, it won’t update the newly configured Activity.

How do we fix this? Android provides a facility for passing a baton from a destroyed Activity instance to a newly created instance. We first override onRetainNonConfigurationInstance:

@Override
public Object onRetainNonConfigurationInstance() {
  return myBaton;
}

Then in onCreate, we can call getLastNonConfigurationInstance to get our baton. It’ll be null if we’re starting up for some reason beyond a configuration change. In our case, we’d like to update the receiver to pass along the result received event to the new Activity.

Saving instance state

Let’s trigger another problem. If we fail authorization and then rotate the screen, we lose our status message. Ack! Let’s get it back. We want to persist this message across configuration changes. I suppose we could do it through onRetainNonConfigurationInstance, but Android recommends a different approach for so-called instance state. We don’t want to use preferences or a database, because this message is not truly persistent. For this kind of data, we can override onSaveInstanceState. Then in onRestoreInstanceState, we can restore the status.

The default implementations of these automatically save and restore the state of all views with IDs. At least, that’s what the documentation says. It’s really (effectively) a big lie. TextView, for instance, doesn’t automatically store its text on its onSaveInstanceState. However, it does support the freezeText attribute in XML, which forces the text to be saved. This is probably a better route than overriding on{Save,Restore}InstanceState.

Adding a ListActivity

I’ve drafted up a skeleton list activity. This will also need to ask the IntentService to go fetch the events and act as a ResultReceiver to populate it. We’ll get the data back as a JSON array.