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: AsyncTask
, IntentService
, Loader
, AsyncQueryHandler
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 theandroid.widget
andandroid.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.
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.
-
Looper
runs a message loop on a thread using theMessageQueue
. -
MessageQueue
holds a list of messages to be dispatched by theLooper
. -
Handler
allows the sending and processing ofMessage
andRunnable
to theMessageQueue
. 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
.
// Handler uses current Thread's Looper Handler handler = new Handler(); // Handler uses the Looper provides Handler handler = new Handler(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 Handler
s. 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.
// Preparing a Thread for HaMeR class LooperThread extends Thread { public Handler mHandler; public void run() { // adding and preparing the Looper Looper.prepare(); // the Handler instance will be associated with Thread’s Looper mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; // Starting the message queue loop using the Looper Looper.loop(); } }
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.
// The HandlerThread class includes a working Looper public class HamerThread extends HandlerThread { // you just need to add the Handler private Handler handler; public HamerThread(String name) { super(name); } }
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.
// Declaring a Runnable Runnable r = new Runnable() { @Override public void run() { // the task goes here } };
There are multiple options to post a Runnable
on a Handler
.
-
Handler.post(Runnable r)
: add theRunnable
to theMessageQueue
. -
Handler.postAtFrontOfQueue(Runnable r)
: add theRunnable
at the front of theMessageQueue
. -
Handler.postAtTime(Runnable r, long timeMillis)
: add theRunnable
on theMessageQueue
to be called at a specific time. -
Handler.postDelayed(Runnable r, long delay)
: add theRunnable
to the queue to be called after a specific amount of time has elapsed.
// posting a Runnable on a Handler Handler handler = new Handler(); handler.post( new Runnable() { @Override public void run() { // task goes here } });
It is also possible to use the default UI handler to post a Runnable
calling Activity.runOnUiThread()
.
// posting Runnable using the UI Handler Activity.runOnUiThread( new Runnable() { @Override public void run(){ // task to perform } });
It's important to keep in mind some things about Runnable
s. 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 theMessage
-
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.
int what = 0; String hello = "Hello!"; // Obtaining Message associated with background Thread Message msg = handlerBGThread.obtainMessage(what, hello); // Sending the Message to background Thread handlerBGThread.sendMessage(msg);
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 Runnable
s, there are multiple options to send Message
s:
-
Handler.sendMessage( Message msg )
: add aMessage
to theMessageQueue
. -
Handler.sendMessageAtFrontOfQueue( Message msg )
: add aMessage
to the front of theMessageQueue
.
-
Handler.sendMessageAtTime( Message msg, long timeInMillis )
: add aMessage
to the queue at a specific time.
-
Handler.sendMessageDelayed ( Message msg, long timeInMillis )
: add aMessage
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.
public class MessageHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { // handle 'Hello' msg case 0:{ String hello = (String) msg.obj; System.out.println(hello); break; } } } }
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 Runnable
s with delays.
See you soon!
Comments