Making Asynchronous Calls with AsyncTask

Version 2 of the AWS Mobile SDK

  • This article and sample apply to Version 1 of the AWS Mobile SDK. If you are building new apps, we recommend you use Version 2. For details, please visit the AWS Mobile SDK page.
  • This content is being maintained for historical reference.

When you launch an Android application, a single thread is created, called the main thread or UI thread. This thread is responsible for event dispatching and user interaction. If you make a call that takes a long time to finish—say, uploading a large image into Amazon S3, on the UI thread—then the UI thread is blocked and the application performs badly. If the UI thread is blocked for more than a certain amount of time (usually 5 seconds), an "Application Not Responding" (ANR) dialog appears. Here is a simple example that demonstrates this problem.

We simulate a long running synchronous call by sending the thread to sleep for 10 seconds. Once the onClick event is triggered, the UI thread will be blocked for 10 seconds.

@Override
public void onClick(View v) {
    try {
        // Block the thread to sleep for 10 seconds.
        Thread.sleep(10000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

To avoid this problem, we can make the call asynchronously by executing it in a worker thread and then publish the result back to the UI thread for a UI update. AsyncTask is a handy class in Android that simplifies the process of an asynchronous call. It allows you to make the call without handling threads and handlers yourself. The following example shows how to make a call asynchronously using AsyncTask.

In this example, we upload an image picked from the gallery into an S3 bucket. To make this happen, we define a class by subclassing AsyncTask and overriding steps in its execution cycle. We then initialize this class and kick off the task on the UI thread.

Subclassing AsyncTask

AsyncTask is an abstract class with three generic types Params, Progress, and Result. We subclass AsyncTask and define S3PutObjectTask like this.

private class S3PutObjectTask extends AsyncTask<Uri, Integer, S3TaskResult> {
    // Track uploading progress
    ProgressDialog dialog;
}

In the generic types list:

  • Param is the URI of the image to be uploaded. It is sent to the task upon execution.
  • Integer is the number of bytes that have been transferred to S3. It is published when the task is running in the background.
  • S3TaskResult is a simple class that consists of a Uri and an error message. It is the returned value of the task.
private class S3TaskResult {
    Uri uri = null;
    String errorMessage = null;
    // Getters and setters
    ...
}

Note that not all generic types are necessary. If you don't need a type, just set it to Void.

Defining Four Steps

There are four steps in the execution cycle of AsyncTask: onPreExecute(), doInBackground(Params...), onProgressUpdate(Progress...), and onPostExecute(Result). Among these steps, only doInBackground is invoked on a worker thread, while the others are invoked on the UI thread. Not all of these steps are necessary. At minimum, we should override doInBackground.

onPreExecute is invoked before the task is executed. We can set up the progress dialog here.

@Override
protected void onPreExecute() {
    // Set up progress dialog
    dialog = new ProgressDialog(S3UploaderActivity.this);
    dialog.setMessage(getString(R.string.uploading));
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    dialog.setCancelable(false);
    dialog.show();
}

doInBackground is invoked on a worker thread. A synchronous call that takes a long time to run should be executed here. In our case, it's the put object request. So next, we build a PutObjectRequest and send it to AmazonS3Client.

@Override
protected S3TaskResult doInBackground(Uri... uris) {
    // Get the file path of the image from the given URI
    ...

    File imageFile = new File(filePath);
    dialog.setMax((int) imageFile.length());

    S3TaskResult result = new S3TaskResult();
    result.setUri(uris[0]);

    // Put the image data into S3.
    try {

        // Create an S3 bucket
        s3Client.createBucket(Constants.getPictureBucket());

        // Content type is determined by file extension.
        PutObjectRequest por = new PutObjectRequest(
            Constants.getPictureBucket(), Constants.PICTURE_NAME,
            imageFile);
        // Send the request
        s3Client.putObject(por);
    } catch (Exception exception) {
        result.setErrorMessage(exception.getMessage());
    }

    return result;
}

To track upload progress, we add a ProgressListener to the request. The bytes transferred can be found in the ProgressEvent fired to the listener. We then call publishProgress to publish the total number of bytes, which is later passed to onProgressUpdate.

// Set a progress listener to the request in order to update
// the progress bar.
por.setProgressListener(new ProgressListener() {
    int total = 0;

    @Override
    public void progressChanged(ProgressEvent pv) {
        total += (int) pv.getBytesTransfered();
        publishProgress(total);
    }

});

We update the progress in the dialog box when onProgressUpdate is invoked in doInBackground.

@Override
protected void onProgressUpdate(Integer... values) {
    // Update the progress bar
    dialog.setProgress(values[0]);
}

onPostExecute is invoked after a synchronous call finishes in doInBackground. onPostExecute receives the result returned by doInBackground. What we do here is to show an alert dialog when the error message is set in the result.

@Override
protected void onPostExecute(S3TaskResult result) {
    dialog.dismiss();
    if (result.getErrorMessage() != null) {
        displayErrorAlert(getString(R.string.upload_failure_title),
            result.getErrorMessage());
    }
}

Executing the Task

We should initialize an S3PutObjectTask object on the UI thread. When we run the task, we should also invoke execute(Params...) on the UI thread. If we want to upload another image, we should create a different S3PutObjectTask object rather than invoking execute(Params...) with a different image URI.

@Override
public void onClick(View v) {
    new S3PutObjectTask().execute(selectedImage);
}

Next Steps

The activity that starts an AsyncTask class may be destroyed due to configuration changes, e.g., a screen orientation change. The worker thread still runs in the background but will have out-of-date references to UI elements, so any attempts to update the UI are likely to cause unexpected errors. We do not cover this situation here, but a suggested solution is given in this open source application: Shelves.

If you have any questions, please leave a comment below.

Comments