Looper, Handler, MessageQueue, and HandlerThread are useful in asynchronous programming.
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:
- Client initializes Handler and Looper
- Client sends messages to Handler
- Handler posts messages to Looper’s MessageQueue
- Looper processes messages in MessageQueue
- 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) } } }