Looper, Handler, MessageQueue, and HandlerThread are useful in asynchronous programming.

Handler and Looper working
Handler and Looper working

Message

Message is a data object. It contains payload of the message. Message class implements Parcelable interface (so that it can be put in extras to bundles and intents). Primarily it is:

  • int what — message code to understand what this message is about
  • int arg1, arg2 — simple integer primitives to store some payload values
  • Object obj — custom object, which we might send
  • Runnable callback — custom action which we might send

This means that we might either send some action inside the Message (using callback) or send some arbitrary message (using what). If we want to obtain Message with what and some custom obj, then we’ll write:

val msg = Message.obtain(handler, what, object)

MessageQueue

MessageQueue holds the list of messages to be dispatched by a Looper. Messages are added to a MessageQueue by Handler objects associated with the Looper. You can retrieve the MessageQueue for the current thread with Looper.myQueue().

Looper

It is a worker that serves a MessageQueue for current thread. Looper loops through message queue and sends messages to corresponding handlers to process. Any thread can have only one unique Looper. The Looper class is usually used in conjunction with a HandlerThread (a subclass of Thread).

It transforms a normal thread, which terminates when its run() method returns, into something that runs continuously until Android app is running. It provides a queue where jobs to be done are enqueued.

Important Looper methods

  • prepare : Initialize thread and attaches Looper to it.
  • loop : It starts event loop. It make the thread running infinity with a MessageQueue.
  • quit : These methods are used to stop event loop. It is not thread safe as it will terminate processing the queue and some events might be left unprocessed.
  • quitSafely : It waits until all the messages are processed and then terminates.

Handler

Handler allows you to send and process Message and Runnable objects associated with. A Handler is a utility class that facilitates interacting with a Looper—mainly by posting messages and Runnable objects to the thread’s MessageQueue. When a Handler is created, it is bound to a specific Looper (and associated thread and message queue).

In typical usage, you create and start a HandlerThread, then create a Handler object (or objects) by which other threads can interact with the HandlerThread instance. The Handler must be created while running on the HandlerThread. Basically it works the following way:

  1. Client initializes Handler and Looper
  2. Client sends messages to Handler
  3. Handler posts messages to Looper’s MessageQueue
  4. Looper processes messages in MessageQueue
  5. When message is ready to be processed, Looper sends it back to Handler, which can handle message.

The main thread (a.k.a. UI thread) in an Android application is set up as a handler thread before your application instance is created.

Scheduling messages

It is accomplished by following functions

  • post(Runnable)
  • postAtTime(Runnable, long)
  • postDelayed(Runnable, long)
  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message, long)
  • sendMessageDelayed(Message, long)

The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received. The sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the handler’s handleMessage(Message) method.

Example

Below example demonstrate how to create looper, handler and message queue. A thread gets a Looper and MessageQueue by calling Looper.prepare(). Looper.loop() must be called to start the associated looper.

class LooperThread extends Thread {
      public Handler mHandler; 

      public void run() { 
          Looper.prepare();
          
          // Create handler
          mHandler = new Handler() { 
              public void handleMessage(Message msg) { 
                 // process incoming messages, this will run in non-ui/background thread
              } 
          }; 

          Looper.loop();
      } 
  } 

There are two ways to send messages to the MessageQueue i.e. Message and Runnable. Below example illustrates this.

// Message
Message msg = new Message();
msg.obj = "Ali send message";
handler.sendMessage(msg);

// A runnable can also be posted in the MessageQueue.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
        // this will run in the main thread
    }
});

In the above example, we create a Handler and provide Looper associated with the main thread. When we post the Runnable, it gets queued in the main thread’s MessageQueue and then executed in the main thread.

Creating HandlerThread

One of the ways to create the HandlerThread is to subclass it as shown in below example. Handler object is created in onLooperPrepared(). So, that Handler can be associated with that Looper. A Looper is only prepared after HandlerThread’s start() is called i.e. after the thread is running.

private class MyHandlerThread extends HandlerThread {

    Handler handler;

    public MyHandlerThread(String name) {
        super(name);
    }

    @Override
    protected void onLooperPrepared() {
        handler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // process incoming messages, this will run in non-ui/background thread
            }
        };
    }
}

Complete Workflow

Below example shows the interaction of all the component in an Activity.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var backgroundHandler: Handler? = null

        // Creating a background thread.
        val backgroundThread = Thread {

            // Creating a Looper for this thread.
            Looper.prepare()

            // Looper.myLooper() gives you Looper for current Thread.
            val myLooper = Looper.myLooper()!!

            // Creating a Handler for given Looper object.
            backgroundHandler = Handler(myLooper,
                object : Handler.Callback {

                    // Processing incoming messages for this Handler.
                    override fun handleMessage(msg: Message?): Boolean {

                        // Receiving extras from Message
                        val bundle: Bundle? = msg!!.data

                        Log.d("", "Handler:: Extras: ${bundle}")

                        Log.d("", "Handler:: Background Thread ID ${Thread.currentThread().id}")                        

                        //myLooper.quit()
                        return true
                    }
                })

            Looper.loop()
        }

        backgroundThread.start()


        // Click listener on a Button
        btn_launch.setOnClickListener {
            Log.d("", "Handler:: UI Thread ID ${Thread.currentThread().id}")

            // Executing code on backgroundThread using Handler.
            backgroundHandler!!.post {
                Log.d("", "Handler:: Background Thread ID ${Thread.currentThread().id}")
            }

            // Sending data on backgroundThread using Message object.
            val extras = Bundle()
            extras.putInt("PRICE", 100)
            extras.putString("PRODUCT_NAME", "Table Lamp")

            val message = Message.obtain(backgroundHandler)
            message.data = extras

            backgroundHandler?.sendMessage(message)
        }
    }

}