Understanding Activity.runOnUiThread()

Yossi Segev
5 min readAug 30, 2017

--

When developing Android applications we always have to be mindful about our application Main Thread.

The Main Thread is busy dealing with everyday stuff such as drawing our UI, responding to user interactions and generally, by default, executing (most) of the code we write.

A good developer knows she/he needs to off load heavy tasks to a worker Thread to avoid clogging the Main Thread and allow a smoother user experience and avoid ANR.
But, when the time comes to update the UI we must “return” to the Main Thread, as only he’s allowed to touch and update the application UI.

A common way to achieve this is to call the Activity’s runOnUiThread() method:

runOnUiThread(new Runnable() {
void run() {
// Do stuff…
}
});

This will magically cause the Runnable code to get executed on the Main Thread.

Magical things are great… but only outside of our app source code.
In this blog post, I will try to shed some light on what is actually going on inside runOnUiThread() and (hopefully) ruin the magic.

Ruining the magic

Let‘s peek at the relevant parts of the Activity source code:

final Handler mHandler = new Handler();
private Thread mUiThread;
// ...public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
// ...}

Seems pretty straightforward, first we check if the current Thread we’re running on is the Main Thread.
If it is the Main Thread — Great! Just invoke the Runnable run() method.

But what if it’s not the Main Thread?
In that case, we call mHandler.post() and pass in our Runnable.
So what is actually happening here?

Before we can answer that we really should talk about something called a Looper.

It’s all starts with a Looper

When we create a new Java Thread we override its run() method.
A simple Thread implementation could look like that:

public class MyThread extends Thread {

@Override
public void run() {
// Do stuff...
}
}

Take a good look at that run() method, when the Thread finishes executing every statement inside of it, the Thread is Done. Finished. Useless.

If we want to reuse a Thread (a good reason would be to avoid spawning new Threads and reduce our memory footprint) we have to keep him alive and waiting for new instructions. A common way to achieve this is to create a loop inside the Thread’s run() method:

public class MyThread extends Thread {

private boolean running;

@Override
public void run() {
while (running) {
// Do stuff...
}
}
}

As long as the while loop is running (ie: The run() method hasn’t finished yet) — that Thread is staying alive.

That’s exactly what a Looper is doing:
The Looper is.. well, LOOPING, and keeping its Thread alive.

Some things about the Looper worth mentioning:

  • Threads don’t get a Looper by default.
  • You can create and attach a Looper to a Thread.
  • There can only be one Looper per Thread.

So, Let’s go ahead and replace the while loop with a Looper implementation:

public class MyThread extends Thread {

@Override
public void run() {
Looper.prepare();
Looper.loop();
}
}

This is actually really simple:

Calling Looper.prepare() checks if there is no Looper already attached to our Thread (remember, only one Looper per Thread) and then creating and attaching a Looper.

Calling Looper.loop() cause our Looper to start looping.

So, Now the Looper is looping and keeping our Thread alive, but there is no point in keeping a Thread alive without passing in instructions, work, things for our Thread to actually do…

Luckily, the Looper isn’t just looping.
When we created the Looper a work queue was created with him.
That queue is called the MessageQueue because he holds Message objects.

What is a Message?

These Message objects are actually sets of instructions.
They can hold data such as Strings and integers or they can hold tasks AKA Runnables.

So, when a Message enters Thread’s Looper Message queue, and when the Message turn (it is a queue after all) has come — The Message instructions are executed on that very Thread. Which means that…

If we want a Runnable to be executed on a specific Thread, all we have to do is to put that Runnable into a Message and add that Message to the Thread’s Looper Message queue!

Great! How do we do that?
That’s Easy. We use a Handler. (You can see where this is going, right?)

The Handler

The Handler is doing all the hard work.

He is responsible for adding Messages to a Looper’s queue and when their time has come he is responsible for executing the same Messages on the Looper’s Thread.

When a Handler is created he is pointed toward a specific Looper. (ie: pointed toward a specific Thread)

There are two ways to create a Handler:

  1. Specify its Looper in the constructor:
    Handler handler = new Handler(Looper looper);
    Now the handler is pointed toward the Looper (actually, the Looper MessageQueue) we provided.
  2. Use empty constructor:
    Handler handler = new Handler();
    When using the empty constructor the Handler will automatically point toward the Looper attached to the current Thread. How convenient!

The Handler has convenience methods to create Messages and automatically add them to its Looper queue.
For example, the post() method creates a Message and add it to the end of the Looper’s queue.

If we want this Message to hold a task (a Runnable) we simply pass the Runnable into the post() call:

handler.post(new Runnable() {
@Override
public void run() {
// Do stuff...
}
});

Looks familiar?

Revisiting the Activity source code

Now we can take a slightly more educated look at runOnUiThread():

final Handler mHandler = new Handler();
private Thread mUiThread;
// ...public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
// ...}

First, a Handler is created with an empty constructor.
Remember: this code is executed on the Main Thread.
That means mHandler is pointed toward the Main Thread Looper.

Yes, The application Main Thread is the only Thread we get with a Looper attached to him by default.

So… when this line is getting executed:

mHandler.post(action);

The Handler is creating a Message that holds our Runnable, and that Message is then added to the Main Thread Looper’s queue,
Where it will stay until the Handler will execute it on its Looper Thread —
The Main Thread.

That’s it! No more magic.

A closure

This blog post goal was to simplify the concept and relationships between the Looper, the MessageQueue, and the Handler.

Actually, There is much (much!) more to it and it would be a crime to leave you without providing some recommended materials for further reading:

Android Handler Internals by Jag Saund

A journey on the Android Main Thread — PSVM by Pierre-Yves Ricau

Understanding Android Core: Looper, Handler, and HandlerThread by Janishar Ali

Happy digging!

--

--