Practical Concurrency on Android With HaMeR

In Understanding Concurrency on Android Using HaMeR, we talked about the basics of the HaMeR (Handler, Message, and Runnable) framework. We covered its options, as well as when and how to use it. 

Today, we’ll create a simple application to explore the concepts learned. With a hands-on approach, we’ll see how to apply the different possibilities of HaMeR in managing concurrency on Android.

1. The Sample Application

Let’s get to work and post some Runnable and send Message objects on a sample application. To keep it as simple as possible, we’ll explore only the most interesting parts. All resource files and standard activity calls will be ignored here. So I strongly advise you to check out the source code of the sample application with its extensive comments. 

HaMeR sample application

The app will consist of:

  • Two activities, one for Runnable another for Message calls
  • Two HandlerThread objects:
    • WorkerThread to receive and process calls from the UI
    • CounterThread to receive Message calls from the WorkerThread
  • Some utility classes (to preserve objects during configuration changes and for layout)

2. Posting and Receiving Runnables

Let's begin experimenting with the Handler.post(Runnable) method and its variations, which add a runnable to a MessageQueue associated with a thread. We'll create an activity called RunnableActivity, which communicates with a background thread called WorkerThread.

The RunnableActivity instantiates a background thread called WorkerThread, passing a Handler and a WorkerThread.Callback as parameters. The activity can make calls on WorkerThread to asynchronously download a bitmap and exhibit a toast at a certain time. The results of the tasks done by the worker thread are passed to RunnableActivity by runnables posted on the Handler received by WorkerThread.

2.1 Preparing a Handler for RunnableActivity

On the RunnableActivity we'll create a Handler to be passed to WorkerThread. The uiHandler will be associated with the Looper from the UI thread, since it's being called from that thread.

2.2 Declaring WorkerThread and Its Callback Interface

The WorkerThread is a background thread where we'll start different kinds of tasks. It communicates with the user interface using the responseHandler and a callback interface received during its instantiation. The references received from the activities are WeakReference<> type, since an activity could be destroyed and the reference lost.

The class offers an interface that can be implemented by the UI. It also extends HandlerThread, a helper class built on top of Thread that already contains a Looper, and a MessageQueue. Hence it has the correct setup to use the HaMeR framework.

2.3 Initializing WorkerThread

We need to add a method to WorkerThread to be called by the activities that prepare the thread's postHandler for use. The method needs to be called only after the thread is started.

On the RunnableActivity we must implement WorkerThread.Callback and initialize the thread so it can be used.

2.4 Using Handler.post() on the WorkerThread

The WorkerThread.downloadWithRunnable() method downloads a bitmap and sends it to RunnableActivity to be displayed in an image View. It illustrates two basic uses of the Handler.post(Runnable run) command:

  • To allow a Thread to post a Runnable object to a MessageQueue associated with itself when .post() is called on a Handler associated with the Thread's Looper.
  • To allow communication with other Threads, when .post() is called on a Handler associated with other Thread's Looper.
  1. The WorkerThread.downloadWithRunnable() method posts a Runnable to the WorkerThread's MessageQueue using the postHandler, a Handler associated with WorkThread's Looper.
  2. When the runnable is processed, it downloads a Bitmap on the WorkerThread.
  3. After the bitmap is downloaded, the responseHandler, a handler associated with the UI thread, is used to post a runnable on the RunnableActivity containing the bitmap.
  4. The runnable is processed, and the WorkerThread.Callback.loadImage is used to exhibit the downloaded image on an ImageView.

2.5 Using Handler.postAtTime() and Activity.runOnUiThread()

The WorkerThread.toastAtTime() schedules a task to be executed at a certain time, exhibiting a Toast to the user. The method illustrates the use of the Handler.postAtTime() and the Activity.runOnUiThread().

  • Handler.postAtTime(Runnable run, long uptimeMillis) posts a runnable at a given time.
  • Activity.runOnUiThread(Runnable run) uses the default UI handler to post a runnable to the main thread.

3. Sending Messages With the MessageActivity & WorkerThread

Next, let's explore some different ways of using MessageActivity  to send and process Message objects. The MessageActivity instantiates WorkerThread, passing a Handler as a parameter. The WorkerThread has some public methods with tasks to be called by the activity to download a bitmap, download a random bitmap, or exhibit a Toast after some delayed time. The results of all those operations are sent back to MessageActivity using Message objects sent by the responseHandler.

3.1 Preparing the Response Handler From MessageActivity

As in the RunnableActivity, in the MessageActivity we'll have to instantiate and initialize a WorkerThread sending a Handler to receive data from the background thread. However, this time we won't implement WorkerThread.Callback; instead, we'll receive responses from the WorkerThread exclusively by Message objects.

Since most of the MessageActivity and RunnableActivity code is basically the same, we'll concentrate only on the uiHandler preparation, which will be sent to WorkerThread to receive messages from it.

First, let's provide some int keys to be used as identifiers to the Message objects.

On MessageHandler implementation, we'll have to extend Handler and implement the handleMessage(Message) method, where all messages will be processed. Notice that we're fetching Message.what to identify the message, and we're also getting different kinds of data from Message.obj. Let's quickly review the most important Message properties before diving into the code.

  • Message.whatint identifying the Message
  • Message.arg1int arbitrary argument
  • Message.arg2int arbitrary argument
  • Message.objObject to store different kinds of data

3.2 Sending Messages With WorkerThread

Now let's get back to the WorkerThread class. We'll add some code to download a specific bitmap and also code to download a random one. To accomplish those tasks, we'll send Message objects from the WorkerThread to itself and send the results back to MessageActivity using exactly the same logic applied earlier for the RunnableActivity.

First we need to extend the Handler to process the downloaded messages.

The downloadImageMSG(String url) method is basically the same as the downloadImage(String url) method. The only difference is that the first sends the downloaded bitmap back to the UI by sending a message using the responseHandler.

The loadImageOnUIMSG(Bitmap image) is responsible for sending a message with the downloaded bitmap to MessageActivity

Notice that instead of creating a Message object from scratch, we're using the Handler.obtainMessage(int what, Object obj) method to retrieve a Message from the global pool, saving some resources. It's also important to note that we're calling the obtainMessage() on the responseHandler, obtaining a Message associated with MessageActivity's Looper. There are two ways to retrieve a Message from the global pool: Message.obtain() and Handler.obtainMessage().

The only thing left to do on the image download task is to provide the methods to send a Message to WorkerThread to start the download process. Notice that this time we'll call Message.obtain(Handler handler, int what, Object obj) on handlerMsgImgDownloader, associating the message with WorkerThread's looper.

Another interesting possibility is sending Message objects to be processed at a later time with the command Message.sendMessageDelayed(Message msg, long timeMillis).

We created a Handler expressly for sending the delayed message. Instead of extending the Handler class, we took the route of instantiating a Handler by using the Handler.Callback interface, for which we implemented the handleMessage(Message msg) method to process the delayed Message.

4. Conclusion

You've seen enough code by now to understand how to apply the basic HaMeR framework concepts to manage concurrency on Android. There are some other interesting features of the final project stored on GitHub, and I strongly advise you to check it out. 

Finally, I have some last considerations that you should keep in mind:

  • Don't forget to consider Android's Activity lifecycle when working with HaMeR and Threads in general. Otherwise, your app may fail when the thread tries to access activities that have been destroyed due to configuration changes or for other reasons. A common solution is to use a RetainedFragment to store the thread and populate the background thread with the activity's reference every time the activity is destroyed. Take a look at the solution in the final project on GitHub.
  • Tasks that run due to Runnable and Message objects processed on Handlers don't run asynchronously. They'll run synchronously on the thread associated with the handler. To make it asynchronous, you'll need to create another thread, send/post the Message/Runnable object on it, and receive the results at the appropriate time.

As you can see, the HaMeR framework has lots of different possibilities, and it's a fairly open solution with a lot of options for managing concurrency on Android. These characteristics can be advantages over AsyncTask, depending on your needs. Explore more of the framework and read the documentation, and you'll create great things with it.

See you soon!

Tags:

Comments

Related Articles