AWS Mobile Blog

Making Asynchronous Calls with Handler

by Yangfan Zhang | on | in S3 | Permalink | Comments |  Share

There are two ways to make asynchronous calls: AsyncTask, and Handler plus Thread. I briefly introduced how to use AysncTask to make asynchronous calls in the previous post. In this post, I will explain making asynchronous calls with Handler. I will also compare AsyncTask with Handler.

What is Handler?

A Handler is associated with a thread that creates the handler. It receives and processes messages and Runnable objects that are sent from a different thread. With Handler, you can run a time-consuming task in a background thread and send messages to the main thread to update the UI. I will use uploading an image to Amazon S3 as an example to explain how to use Handler to make asynchronous calls.

Defining a Thread Handler

An Android application has a default thread — the main thread. When a Handler object is created in onCreate(), it’s bonded to the main thread. You override the handlerMessage(Message msg) method in order to process messages that are sent to the handler. The Message class consists of a user-defined message code what, two integer value arguments arg1 and arg2, and an arbitrary object obj. Not all fields are set by the sender. Usually, at minimum the message code is set to identify an event.

In the process of uploading an image to Amazon S3, you are likely to be interested in three events: when the upload starts, when data is transferring, and when the upload finishes. Therefore, three message codes, S3_UPLOAD_START, S3_UPLOAD_PROGRESS, and S3_UPLOAD_FINISH, are used to identify these events, respectively. Based on the message, you can use a switch statement to perform a corresponding operation.

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case S3_UPLOAD_START:
            // Set up a progress dialog when upload starts
            dialog = new ProgressDialog(...);
            dialog.setMessage(getString(R.string.uploading));
            dialog.setCancelable(false);
            dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            dialog.setMax(msg.arg1);
            dialog.show();
            break;
        case S3_UPLOAD_PROGRESS:
            // Update progress
            dialog.setProgress(msg.arg1);
            break;
        case S3_UPLOAD_FINISH:
            // When upload finishes, dismiss progress dialog
            dialog.dismiss();
            break;
        default:
            break;
        }
    }
}

Running a Task in a Thread

As a general rule, it’s best to not block the main thread in Android. So time-consuming tasks must be run in a background thread. You can extend the Thread class and implement the time-consuming task inside the run() method, which you kick off by calling start(). In order to communicate with the main thread, messages have to be created and sent to the main thread’s handler. Handler has a convenient method, obtainMessage(...), to create a message. You can define message code and include additional data, such as number of bytes that are transferred and error string, to the message. You then send this data to the thread handler.

S3PutObjectThread gets the Uri of an image to be uploaded from its constructor. The upload logic is implemented inside the run() method. You can send an S3_UPLOAD_START message before the upload starts and an S3_UPLOAD_FINISH message when it finishes. A progress listener is attached to the upload request in order to report transfer progress. When the listener is triggered, you compute the total bytes transferred and send an S3_UPLOAD_PROGRESS message with this number to the main thread.

class S3PutObjectThread extends Thread {
    private Uri selectedImage;

    S3PutObjectThread(Uri selectedImage) {
        this.selectedImage = selectedImage;
    }

    @Override
    public void run() {
        // Get the file path of the image from selectedImage
        ...
        File imageFile = new File(filePath);
        Message msg;

        // Notify the main thread that the upload starts
        msg = handler.obtainMessage(S3_UPLOAD_START);
        handler.sendMessage(msg);

        try {
            // Create a S3 bucket if necessary
            ...
            PutObjectRequest por = new PutObjectRequest(bucket, key, imageFile);
            // Attach a progress listener to the request
            por.setProgressListener(new ProgressListener() {
                int total = 0;

                @Override
                public void progressChanged(ProgressEvent pe) {
                    total += (int) pe.getBytesTransfered();
                    // Create a message, and set the total bytes transferred to arg1
                    Message msg = handler.obtainMessage(S3_UPLOAD_PROGRESS, total, 0);
                    // Send progress update to the main thread
                    handler.sendMessage(msg);
                }
            });

            // Send the request
            s3Client.putObject(por);
        } catch (Exception e) {
            // Handle exception here.
            ...
        }

        // Upload finishes
        msg = handler.obtainMessage(S3_UPLOAD_FINISH);
        handler.sendMessage(msg);
    }
}

...

// Create a thread and start it
new S3PutObjectThread(selectedImage).start();

Comparison Between Handler and AsyncTask

AsyncTask is a wrapper of Handler and Thread. It allows you to conveniently make asynchronous calls without handling threads and handlers. You only need to focus on the four steps in the execution cycle of AsyncTask: onPreExecute, doInBackground, onProgressUpdate, and onPostExecute. However, convenience comes with a price. Here are some limitations.

  • An AsyncTask instance has to be initiated and executed in the main thread.
  • AsyncTask instances cannot communicate with each other easily.
  • It’s not easy to schedule AsyncTask to run at a certain time in the future.
  • The execution order of multiple AsyncTask instances may not be what you think. Since the arrival of Honeycomb (Android 3.0, API level 11), multiple AsyncTask instances are executed sequentially by default. If you want to change this behavior, you can have them executed on THREAD_POOL_EXECUTOR by invoking executeOnExecutor.

Handler and Thread are the underlying implementation of AsyncTask. They don’t have the limitations of AsyncTask and give you more control over what you want to do.

  • The initialization and execution of a Thread are not limited to the main thread. They can be done almost everywhere.
  • Communication between threads won’t be a problem with handlers. If you want to send messages to a thread, you can attach a handler to it. There is a handy class HandlerThread that allows you to create a handler for a thread (other than the main thread).
  • Handler has several methods for scheduling messages in the future: sendMessageAtTime, sendMessageDelayed, postMessageAtTime, and postMessageDelay.

The choice between AsyncTask and Handler may vary, depending on specific requirements, legacy code, or personal taste. I hope that after reading this post you have a better understanding of Handler and that you properly choose which way to make asynchronous calls in the future.

If you have any questions, please don’t hesitate to post at Mobile Development Forum.

If you like building mobile applications that use cloud services that our customers use on a daily basis, perhaps you would like to join the AWS Mobile SDK and Tools team. We are hiring Software Developers, Web Developers, and Product Managers.