Loopers and Handlers in Android

Looper and Handler are one of the key low-level components of Android. For example, the UI thread is built with them. However, only a few developers use them directly nowadays. In this article, we’ll try to understand how they work.

Brief overview

The Looper class is essentially an event loop for a thread. There can be at most one Looper per thread.

For each Looper, there is exactly one MessageQueue, which holds the list of messages to be dispatched. We rarely need to use this class directly.

For most of the time, we use the Handler class to send messages to a Looper, and handles the messages when dispatched. There can be as many Handlers as we want for any Looper.

Sitting on top of them, the HandlerThread provides a thread with a Looper. Let’s start from this class.


The following code shows how to use a HandlerThread in a very basic way:

val handlerThread = HandlerThread("my handler thread")

// need to call start() to start the thread

val handler = object :  Handler(handlerThread.looper) {
    override fun handleMessage(msg: Message) {
        println("handle message: $msg")

// the messages will be handled by Handler.handleMessage()
handler.sendMessage(Message.obtain(handler, 777))

// the runnable will just run
handler.post { println("a runnable") }

As we can see here, there are two main types of messages: a Message that will be handled by the Handler, or a Runnable that will just run. We will discuss later how the messages are dispatched in more detail.

A HandlerThread runs as long as the Looper is running. There are two methods to stop the event loop and end the thread:

  1. quit(): discard all messages, and immediately quit the event loop.
  2. quitSafely(): dispatch all messages that are already due, and then quit.


Send messages

As seen in the previous section, there are two main types of messages that Handler can handle. When the user posts a Runnable, it will first be wrapped into a Message:

public class Handler {
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;


Then it goes through the same code path as a regular Message, and eventually asks the MessageQueue to enqueue it:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
    return queue.enqueueMessage(msg, uptimeMillis);

Handle messages

When Looper dispatches a message, Handler.dispatchMessage() will be called (modified for the sake of simplicity):

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        // if the Message is a Runnable, run it
    } else {
        if (mCallback != null) {
            // if the Handler has a Callback (set in constructor),
            // try using it to handle the message
            if (mCallback.handleMessage(msg)) {
        // as a last resort, calls handleMessage()

Note that the dispatchMessage() method is NOT final. So when subclassing Handler, don’t confuse it with handleMessage().


To create our own thread with a Looper, we can take the HandlerThread as a reference:

public class HandlerThread extends Thread {
    public void run() {
        mTid = Process.myTid();
        synchronized (this) {
            mLooper = Looper.myLooper();
        mTid = -1;


The most important things here are: 1) call Looper.prepare() to initialize the looper; 2) call Looper.loop() to run the event loop.

The prepare() method basically ensures there is at most one Looper per thread, stores the created Looper in a ThreadLocal, and creates a MesssageQueue:

public final class Looper {
    public static void prepare() {

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        sThreadLocal.set(new Looper(quitAllowed));

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();


Here, the quitAllowed flag is true for all the loopers in worker threads, meaning we can quit the event loop by calling quit() or quitSafely().

The flag is false only for the main thread for obvious reasons. Note that we should never call Looper.prepareMainLooper() to mark current thread’s looper as the main looper. Fortunately, this method is finally makes as deprecated in R.

After the looper is initialized, we need to call the loop() method to start the event loop, which has quite some logging code. However, the most interesting part is quite easy to understand, as shown below (modified for the sake of simplicity):

public static void loop() {
    final MessageQueue queue = myLooper().mQueue;

    for (;;) {
        // fetches the next message to process
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.

        // dispatches the message

        // recycles the message


MessageQueue has two main methods:

  1. enqueueMessage(): called by Handler to add messages to the queue, and wake up the queue if currently blocked.
  2. next(): called by Looper to get the next message to dispatch. If there is no message to dispatch, it will block waiting for messages.


One thing interesting here is, when the MessageQueue is going to block waiting for more messages to dispatch, it will first run IdleHandlers.

This is quite useful, when e.g. you need to do something on the main thread, but want to do it only when it’s idle. For example, ActivityThread uses this technique to schedule garbage collection

Use case: IPC

IPC (inter-process communication) is one of the most common use cases for Looper and Handler, with the help of Messenger. Under the hood, the Messenger class is simply a wrapper around a Binder, which is used to perform the communication.

First, we create a remote service like below:

class RemoteService : Service() {
    // the Handler to handle the messages
    private val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            println("Message: $msg")

    // the created Messenger will dispatch incoming messages to the handlers
    private val messenger = Messenger(handler)

    // when someone binds to the service, we return the IBinder backing the
    // messenger, so that the peer can use to send messages
    override fun onBind(intent: Intent?): IBinder? = messenger.binder

And we declare the service in AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

    <application ... >
            android:process=":remote_service" />


One thing worth mentioning here is that, no matter whether the service is running in its own process, the code to send and receive messages stay the same.

And finally, we can bind to the service and send messages to it:

class MainActivity : Activity() {
    private var messenger: Messenger? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            // when the service is connected, we create the Messenger to send messages
            messenger = Messenger(service)

            // send a message
            messenger?.send(Message.obtain().apply { what = 777 })

        override fun onServiceDisconnected(name: ComponentName) {
            // nullify the messenger in case the service is disconnected unexpectedly,
            // e.g. the remote process crashes
            messenger = null

    override fun onStart() {

        // bind the service, and start it if needed
        bindService(Intent(this, RemoteService::class.java), connection, BIND_AUTO_CREATE)

    override fun onStop() {
        // disconnect from the service, and nullify the messenger
        messenger = null



Hopefully, this article can give you some idea on how some low-level Android components work.

For most of the time, we probably don’t need to directly work with them, but it’s important and interesting to know how the platform works, and what tools we can use if needed.

See also