What is the relationship between Looper, Handler and MessageQueue in Android?
Asked Answered
T

6

110

I have checked the official Android documentation/guide for Looper, Handler and MessageQueue . But I couldn't get it. I am new to android, and got very confused with these concepts.

Ta answered 13/10, 2012 at 23:29 Comment(0)
J
114

A Looper is a message handling loop: it reads and processes items from a MessageQueue. The Looper class is usually used in conjunction with a HandlerThread (a subclass of Thread).

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, although once created there is no restriction on what threads can use the Handler's scheduling methods (post(Runnable), etc.)

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.

Aside from the class docs, there's a nice discussion of all of this here.

P.S. All the classes mentioned above are in the package android.os.

Jeri answered 13/10, 2012 at 23:52 Comment(2)
@Ted Hopp - Is Looper’s message queue different from Thread’s message queue?Tadpole
@Jack - They are the same thing. The Android API docs for MessageQueue state that a MessageQueue is a "low-level class holding the list of messages to be dispatched by a Looper."Jeri
S
100

Let's start with the Looper. You can understand the relationship between Looper, Handler and MessageQueue more easily when you understand what Looper is. Also you can better understand what Looper is in the context of GUI framework. Looper is made to do 2 things.

1) Looper transforms a normal thread, which terminates when its run() method returns, into something that runs continuously until Android app is running, which is needed in GUI framework (Technically, it still terminates when run() method returns. But let me clarify what I mean, below).

2) Looper provides a queue where jobs to be done are enqueued, which is also needed in GUI framework.

As you may know, when an application is launched, the system creates a thread of execution for the application, called “main”, and Android applications normally run entirely on a single thread by default the “main thread”. But main thread is not some secret, special thread. It's just a normal thread that you can also create with new Thread() code, which means it terminates when its run() method returns! Think of below example.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Now, let's apply this simple principle to Android app. What would happen if an Android app is run on a normal thread? A thread called "main" or "UI" or whatever starts application, and draws all UI. So, the first screen is displayed to users. So what now? The main thread terminates? No, it shouldn’t. It should wait until users do something, right? But how can we achieve this behavior? Well, we can try with Object.wait() or Thread.sleep(). For example, main thread finishes its initial job to display first screen, and sleeps. It awakes, which means interrupted, when a new job to do is fetched. So far so good, but at this moment we need a queue-like data structure to hold multiple jobs. Think about a case when a user touches screen serially, and a task takes longer time to finish. So, we need to have a data structure to hold jobs to be done in first-in-first-out manner. Also, you may imagine, implementing ever-running-and-process-job-when-arrived thread using interrupt is not easy, and leads to complex and often unmaintainable code. We'd rather create a new mechanism for such purpose, and that is what Looper is all about. The official document of Looper class says, "Threads by default do not have a message loop associated with them", and Looper is a class "used to run a message loop for a thread". Now you can understand what it means.

Let's move to Handler and MessageQueue. First, MessageQueue is the queue that I mentioned above. It resides inside a Looper, and that's it. You can check it with Looper class's source code. Looper class has a member variable of MessageQueue.

Then, what is Handler? If there is a queue, then there should be a method that should enable us to enqueue a new task to the queue, right? That is what Handler does. We can enqueue a new task into a queue(MessageQueue) using various post(Runnable r) methods. That's it. This is all about Looper, Handler, and MessageQueue.

My last word is, so basically Looper is a class that is made to address a problem that occurs in GUI framework. But this kind of needs also can happen in other situations as well. Actually it is a pretty famous pattern for multi threads application, and you can learn more about it in "Concurrent Programming in Java" by Doug Lea(Especially, chapter 4.1.4 "Worker Threads" would be helpful). Also, you can imagine this kind of mechanism is not unique in Android framework, but all GUI frameworks may need somewhat similar to this. You can find almost same mechanism in Java Swing framework.

Strasser answered 30/12, 2015 at 7:2 Comment(4)
Best answer. Learned more from this detailed explanation. I wonder if there is some blog post which goes in more detail.Staphylococcus
Can the messages be added to the MessageQueue without using Handler?Tadpole
@Tadpole no they can't be added directly.Jadotville
Made my day...lots of love to you :)Makalu
C
99

It's widely known that it's illegal to update UI components directly from threads other than main thread in android. This android document (Handling Expensive Operations in the UI Thread) suggests the steps to follow if we need to start a separate thread to do some expensive work and update UI after it's done. The idea is to create a Handler object associated with main thread, and post a Runnable to it at appropriate time. This Runnable will be invoked on the main thread. This mechanism is implemented with Looper and Handler classes.

The Looper class maintains a MessageQueue, which contains a list messages. An important character of Looper is that it's associated with the thread within which the Looper is created. This association is kept forever and can't be broken nor changed. Also note that a thread can't be associated with more than one Looper. In order to guarantee this association, Looper is stored in thread-local storage, and it can't be created via its constructor directly. The only way to create it is to call prepare static method on Looper. prepare method first examines ThreadLocal of current thread to make sure that there isn't already a Looper associated with the thread. After the examination, a new Looper is created and saved in ThreadLocal. Having prepared the Looper, we can call loop method on it to check for new messages and have Handler to deal with them.

As the name indicates, the Handler class is mainly responsible for handling (adding, removing, dispatching) messages of current thread's MessageQueue. A Handler instance is also bound to a thread. The binding between Handler and Thread is achieved via Looper and MessageQueue. A Handler is always bound to a Looper, and subsequently bound to the thread associated with the Looper. Unlike Looper, multiple Handler instances can be bound to the same thread. Whenever we call post or any methods alike on the Handler, a new message is added to the associated MessageQueue. The target field of the message is set to current Handler instance. When the Looper received this message, it invokes dispatchMessage on message's target field, so that the message routes back to to the Handler instance to be handled, but on the correct thread. The relationships between Looper, Handler and MessageQueue is shown below:

enter image description here

Comnenus answered 13/10, 2012 at 23:55 Comment(5)
Thanks! but whats the point if the handler first post the message to the message queue and then handle the message from the same queue? why doesn't it just handle the message directly?Ta
@Ta b/c you are posting from one thread (non looper thread) but handling the message in another thread(looper thread)Dasilva
Much better than what's being documented on developer.android.com - but it would be nice to see code for the diagram that you've provided.Ragwort
@numansalati - Can’t Handler post messages from the looper thread?Tadpole
A single vote can't justify the appreciation this answer deserves :(Ely
L
28

MessageQueue: It is a low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.[3]

Looper: It loops over a MessageQueue which contains the messages to be dispatched. The actual task of managing the queue is done by the Handler which is responsible for handling (adding, removing, dispatching) messages in the message queue.[2]

Handler: It allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue.[4]

When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

Kindly, go through the below image[2] for better understanding.

enter image description here

Linell answered 1/5, 2016 at 13:16 Comment(0)
J
0

Extending the answer, by @K_Anas, with an example, As it stated

It's widely known that it's illegal to update UI components directly from threads other than main thread in android.

for instance if you try to update the UI using Thread.

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

your app will crash with exception.

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

in other words you need to use Handler which keeps reference to the MainLooper i.e. Main Thread or UI Thread and pass task as Runnable.

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
Jadotville answered 17/1, 2020 at 15:31 Comment(0)
P
0

One of the simple answer I learned!

MessageQueue: It holds message
 


Handler -> it puts message into messageQueue via looper



looper -> it doesn’t let thread die by putting a infinite loop, Main thread has its own looper. It deque messagequeue and sends them to corresponding handler or execute via thread



looper.quit => thread stops

Example


    class SimpleWorker : Thread() {
    private val TAG = "SimpleWorker"
    private val alive = AtomicBoolean(true)
    private val messageQueue: ConcurrentLinkedQueue<Runnable> = ConcurrentLinkedQueue() //Runnable to execute their run method 

    fun startThread() {
        start()
    }

    override fun run() {
        super.run()
        while (alive.get()) { // something like looper
            sleep(2000)
            Log.d(TAG, "Running ...")
            val task: Runnable? = messageQueue.poll()
            if(task != null) {
                task
                task.run()
            }
        }
        Log.d(TAG, "Stopped")
    }

    fun execute(task: Runnable): SimpleWorker {
        queue.add(task)
        return this
    }

    fun quit() {
        alive.set(false)
    }
 }

Here's how this thread will interact with Main Thread via handler


class MainActivity : AppCompatActivity() {

    private val simpleWorker by lazy { SimpleWorker() }
    private val handler by lazy {
        object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                Log.d("MainActivity", "Message: ${msg.obj} Received at ${Thread.currentThread().name}")
            }
        }
    }

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

        simpleWorker.startThread()

        simpleWorker.execute {
            try {
                Thread.sleep(1000)
                Log.d("SimpleWorker", "Task 1 ${Thread.currentThread().name}")

                val message = Message.obtain()
                message.obj = "Task 1 Sent from ${Thread.currentThread().name}"
                handler.sendMessage(message)

            } catch (e: Exception) {
                e.printStackTrace()
            }
        }.execute {
            try {
                Thread.sleep(1000)
                Log.d("SimpleWorker", "Task 2 ${Thread.currentThread().name}")

                val message = Message.obtain()
                message.obj = "Task 2 Sent from ${Thread.currentThread().name}"
                handler.sendMessage(message)

            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

    }
}

Here's output enter image description here

Prolongation answered 14/4 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.