Understanding Concurrency on Android Using HaMeR

1. Introduction

Everyone who tries Android development discovers the importance of concurrency. The only way to create a responsive app is by leaving the UI Thread as free as possible, letting all the hard work be done asynchronously by background threads.

Due to the design of Android, managing threads using only the java.lang.thread and java.util.concurrent packages could be really difficult. Using the low-level threading packages with Android means that you have to worry about a lot of tricky synchronization to avoid race conditions. Fortunately the folks at Google did the hard work and built some great tools to make our jobs easier: AsyncTaskIntentServiceLoaderAsyncQueryHandler and CursorLoader are all useful, as well as the HaMeR classes Handler, Message, and Runnable. There are lots of great options for you to choose from, each with its pros and cons.

A lot has been said about the AsyncTask object, and many people use it as a silver bullet solution for concurrency on Android. It is extremely useful for short operations, easy to implement, and probably the most popular approach to concurrency on Android. If you do want to learn more about AsyncTask, check out the following Envato Tuts+ posts.

However, AsyncTask shouldn’t be the only tool on your tool belt.

For long-duration operations, for complex concurrency problems, or to achieve more efficiency in some situations, you should choose another solution. If you need more flexibility or efficiency than AsyncTask provides, you could use the HaMeR (Handler, Message & Runnable) framework. In this tutorial we’ll explore the HaMeR framework, one of the most powerful concurrency models available on Android, and we'll learn when and how to use it. In a follow-up tutorial, I'll show you how to code an application to try out some possibilities of HaMeR.

The following section will introduce the importance of background threads for the Android system. If you’re familiar with this concept, feel free to skip it and go directly to the discussion of the HaMeR framework in section 3.

2. Responsiveness Through Background Threads

When an Android application is started, the first thread spawned by its process is the main thread, also known as the UI thread, which is responsible for handling all the user interface logic. This is the most important thread of an application. It is responsible for handling all user interaction and also 'tying' the application's moving parts together. Android takes this very seriously, and if your UI thread is stuck working on a task for more than a few seconds, the app will crash.

[The UI thread] is very important because it is in charge of dispatching events to the appropriate user interface widgets, including drawing events. It is also the thread in which your application interacts with components from the Android UI toolkit (components from the android.widget and android.view packages). As such, the main thread is also sometimes called the UI thread. — Processes and Threads, Android Developer Guide

The problem is that almost all code in an Android application will be executed on the UI thread by default. Since the tasks on a thread are done sequentially, this means that your user interface could ‘freeze’, becoming unresponsive while it is processing some other work.

Leave the UI Thread as free as possible using Background Threads

Long-running tasks called on the UI will probably be fatal to your app, and an ANR (Application Not Responding) dialog will appear. Even small tasks can compromise the user experience, hence the correct approach is to remove as much work as possible from the UI Thread using background threads. As said before, there are many ways to solve this issue, and we'll explore the HaMeR framework, one of the core solutions provided by Android to address this situation.

3. The HaMeR Framework

The HaMeR framework allows background threads to send messages or post runnables to the UI thread and to any other thread's MessageQueue via handlers. HaMeR refers to Handler, Message, & Runnable. There are also some other important classes that work together with the HaMeR: Looper and MessageQueue. Together, those objects are responsible for facilitating thread management on Android, taking care of synchronization, and providing easy methods for background threads to communicate with the UI and with other threads.

Here's how the classes in the HaMeR framework fit together.

The HaMeR framework
  • Looper runs a message loop on a thread using the MessageQueue.
  • MessageQueue holds a list of messages to be dispatched by the Looper.
  • Handler allows the sending and processing of Message and Runnable to the MessageQueue. It can be used to send and process messages between threads.
  • Message contains a description and data that can be sent to a handler.
  • Runnable represents a task to be executed.

With the HaMeR framework, threads can send messages or post runnable objects either to themselves or to the UI thread. HaMeR also promotes background thread interactions via Handler.

3.1. The Handler Class

Handler is the HaMeR workhorse. It's responsible for sending Message (data message) and post Runnable (task message) objects to the MessageQueue associated with a Thread. After delivering the tasks to the queue, the handler receives the objects from the Looper and processes the messages at the appropriate time using the Handler associated with it.

A Handler can be used to send or post Message and Runnable objects between threads, as long as such threads share the same process. Otherwise it will be necessary to create an Inter Process Communication (IPC), a methodology that surpasses the scope of this tutorial.

Instantiating a Handler

A Handler must always be associated with a Looper, and this connection needs to be made during its instantiation. If you don't provide a Looper to the Handler, it will be bound to the current Thread's Looper.

Keep in mind that a Handler is always associated with a Looper, and this connection is permanent and cannot be changed once established. However, a Looper's thread can have associations with multiple Handlers. It is also important to note that a Looper must be active before its association with a Handler.

3.2. Looper and MessageQueue

The cooperative work between Looper and MessageQueue in a Java thread creates a loop of tasks that are processed sequentially. Such a loop will keep the thread alive while it waits to receive more tasks. A thread can have only one Looper and one MessageQueue associated with it; however, there can be multiple handlers for each thread. The handlers are responsible for processing the tasks on the queue, and each task knows which handler is responsible for its processing.

3.3. Preparing a Thread for the HaMeR

The UI or main thread is the only kind of thread that by default already has a Handler, a Looper, and a MessageQueue. Other threads must be prepared with those objects before they can work with the HaMeR framework. First we need to create a Looper that already includes a MessageQueue and attach it to the thread. You can do this with a subclass of Thread, as follows.

However, it's more straightforward to use a helper class called HandlerThread, which has a Looper and a MessageQueue built into a Java Thread and is ready to receive a Handler.

4. Posting Runnables

The Runnable is a Java interface that has many uses. It could be understood as a single task to be performed on a Thread. It has a single method that must be implemented, Runnable.run(), to perform the task.

There are multiple options to post a Runnable on a Handler.

  • Handler.post(Runnable r): add the Runnable to the MessageQueue.
  • Handler.postAtFrontOfQueue(Runnable r): add the Runnable at the front of the MessageQueue.
  • Handler.postAtTime(Runnable r, long timeMillis): add the Runnable on the MessageQueue to be called at a specific time.
  • Handler.postDelayed(Runnable r, long delay): add the Runnable to the queue to be called after a specific amount of time has elapsed.

It is also possible to use the default UI handler to post a Runnable calling Activity.runOnUiThread().

It's important to keep in mind some things about Runnables. Unlike a Message, a Runnable cannot be recycled—once its job is done, it's dead. Since it is part of a standard Java package, a Runnable doesn't depend on Handler and can be called on a standard Thread using the Runnable.run() method. However, this approach doesn't have anything to do with the HaMeR framework and won't share any of its advantages.

5. Sending Messages

The Message object defines a message containing a description and some arbitrary data that can be sent and processed via Handler. The Message is identified with an int defined on Message.what(). The Message can hold two other int arguments and an Object to store different kinds of data.

  • Message.what: int identifying the Message
  • Message.arg1: int arbitrary argument
  • Message.arg2: int arbitrary argument
  • Message.obj: Object to store different kinds of data  

When you need to send a message, instead of creating one from scratch, the recommended approach is to retrieve a recycled one directly from the global pool with the Message.obtain() or Handler.obtainMessage() commands. There are some different versions of those methods that allow you to get a Message according to your need.

A common use of Handler.obtainMessage() is when you need to send a message to a background thread. You'll use the Handler associated with that thread's Looper to obtain a Message and send it to the background thread, as in the example below.

There are lots of cool methods on the Message class, and I advise you to take a closer look at the documentation.

5.1. sendMessage() Options

Similarly to how we can post Runnables, there are multiple options to send Messages:

  • Handler.sendMessage( Message msg ): add a Message to the MessageQueue.
  • Handler.sendMessageAtFrontOfQueue( Message msg ): add a Message to the front of the MessageQueue.
  • Handler.sendMessageAtTime( Message msg, long timeInMillis ): add a Message to the queue at a specific time.
  • Handler.sendMessageDelayed ( Message msg, long timeInMillis ): add a Message to the queue after a specific amount of time has passed.

5.2. Handling Messages With Handler

The Message objects dispatched by Looper are processed by the handler with the method Handler.handleMessage. All that you have to do is extend the Handler class and override this method to process the messages.

6. Conclusion

The HaMeR framework can help improve your Android application's concurrent code. It may seem confusing at first when compared with the simplicity of an AsyncTask, but HaMeR's openness can be an advantage, if used correctly. 

Remember:

  • Handler.post() methods are used when senders know what operations to perform.
  • Handler.sendMessage() methods are used when the receiver knows what operation to perform.

To learn more about threading in Android, you may be interested in the book Efficient Android Threading: Asynchronous Processing Techniques for Android Applications by Anders Goransson.

6.1. What's Next?

In the next tutorial, we'll continue exploring the HaMeR framework with a hands-on approach, by building an application that demonstrates different ways of using this Android concurrency framework. We'll create this app from the ground up, trying different possibilities like communication between Threads, talking with the UI thread, as well as sending messages and posting Runnables with delays. 

See you soon!

Tags:

Comments

Related Articles