Android Loopers and Handlers: The Journey to Implement a Worker Thread.
In this post, I’m going to talk about Loopers and Handlers in Android. Hopefully, after reading this post you’ll know when and how to use them.
In order to get to know Loopers and Handlers, we are going to implement a worker thread but before that let’s get familiar with the Thread
class.
Thread
Java/Kotlin has a class named Thread
which is used to execute some code off the main thread. To use the Thread
class we need to do 3 things:
- Create a new class inheriting from
Thread
- Override the
run
method - Create a new instance of our class and call its
start
method
This is how it’s done in code:
After calling start
the execution of the thread starts.
One important note about MyThread
is that as soon as run
is finished, the thread is destroyed.
Now suppose that we don't want our thread to be destroyed when it is done. In other words, when the thread is done with its current task, instead of being destroyed, it waits until another task is available for execution. Basically, we want to have a worker thread.
Worker Thread
A worker thread has a queue of tasks. When the thread is finished with its current task it picks the next task from the queue and executes it. When the queue of tasks is empty then the thread keeps waiting until a task is available to execute.
What we’re going to do is to implement a worker thread in three different ways. From the most cumbersome to the most elegant!
Handcrafted Worker Thread
In order to implement a worker thread from scratch, using the Thread
class, we need to do a couple of things:
- Putting an infinite loop inside the
run
method - Creating a queue for keeping track of the tasks to execute
- Inside the infinite loop, we get the next task from the queue and execute it
- Find a way to break out of that infinite loop so that we can stop the worker thread when we are done.
The following code does all of the above.
To add a task to the queue of the worker thread we call execute
. Calling quit
finishes the worker thread. We can use the HandcraftedWorkerThread
like the following.
Our worker thread works but it has two problems:
- When there is no task to execute, the while loop inside the
run
method is constantly checking the value ofisAlive
. This is busy waiting and as we all know we are wasting CPU cycles. - We are doing too much work to implement a worker thread! Actually not too much really but we can avoid things like managing the queue of tasks ourselves.
Now it’s time for a better implementation of a worker thread.
Looper Worker Thread
Before implementing the second version of our worker thread we need to get familiar with two classes.
Looper
Looper
class does two things:
- It converts an ordinary thread (which is destroyed when its
run
method is finished) to a thread that can stay alive as long as the Android app is alive. - It has a queue of tasks to execute.
Handler
We can not directly add a task to the queue of a Looper. Instead, we should use an instance of a Handler
to do the trick. In other words, we use a Handler to add a task to the queue of a Looper.
Now we can implement the second version of our worker thread using a Looper and a Handler.
A couple of notes about the above implementation:
- To convert an ordinary thread to a thread that can run forever we need to call
Looper.prepare()
and thenLooper.loop()
. Also,prepare
should always be called after thestart
method of the thread. Otherwise, it will throw an exception. - To communicate with the queue of the Looper we have created an instance of
Handler
and passed the Looper of the current thread to the Handler. - To add a task to the queue of the Looper we call
Handler.post()
and pass it aRunnable
. - When we are done with the worker thread we should call the
quit
method to stop its associated Looper.
We can use the LooperWorkerThread
just like how we did with the HandcraftedWorkerThread
.
With this implementation, we eliminated the busy waiting and also got rid of manually managing the queue of tasks.
We can still do better than this! Here is how.
Handler Worker Thread
To create a worker thread we can inherit from HandlerThread
instead Thread
. This makes things a lot easier. The reason is that HandlerThread
has an inner Looper so we don't need to create one manually.
What follows is the implementation of a worker thread using a HandlerThread
.
Some notes about the code above:
- As stated earlier, because
HandlerThread
has an inner Looper there is no need to create one explicitly. - To communicate with the Looper of
HandlerThread
we have created a Handler. Note that when we create an instance of Handler without passing a Looper to its constructor, the Handler implicitly uses the Looper of the current thread. Here, the Handler will be associated with the Looper of theHandlerThread
. - We do a check inside
execute
to make sure thathandler
is initialized before calling itspost
method. The reason is that this way we prevent a runtime error if, by any chance,execute
is called beforeonLooperPrepared
callback is called. - We have created the Handler inside the
onLooperPrepared
method. The reason is that we should create the Handler only when the Looper is prepared. When the Looper ofHandlerThread
is prepared theonLooperPrepared
is called and that’s where we instantiate the Handler.
And this is it. Just compare HandlerWorkerThread
with HandcraftedWorkerThread
to appreciate what we achieved!
The Main Thread of an Android App
Now that we know about Loopers and Handlers we can talk a little bit about the main thread of an Android app. This thread is also called the UI thread as well.
Each Android app has a single main thread. The main duty of this thread is handling the user’s interactions with the app like clicking a button, rendering a list of items, and so on.
Suppose that the main thread is an ordinary thread. One that gets destroyed when its run
method is finished. The user starts the app, the main thread starts executing, the UI of the first screen of the app is displayed and the main thread is destroyed!
Does that sound weird? Yes! Because that’s not what happens in reality. What actually happens is that the main thread is alive as long as the app is alive. So the main thread of an Android app is not an ordinary thread. It’s a worker thread. You see, it’s a worker thread!
Because the main thread of an Android app is a worker thread that means it has a Looper associated with it and there is a queue of tasks. All interactions of the user with the app like clicking a button, scrolling a list, and so on are added as a task to the queue of the main thread and are executed one by one.
Congrats! By now you should have a good grasp of Loopers, Handlers, and worker threads.
You can access the source code of this post from the following link.