5

Today I read some blogs and source code about how Handler & Looper work together.

Based on what I've learnt, we can have only one Looper on each thread by using the ThreadLocal magic. Usually Handler is initiated in main thread, or else you must manually start or saying, prepare the Looper on a separate thread and then loop it up.

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        Looper.loop();
    }
}

What really confused me was the loop() in main thread. As I read this in the source code of Looper. It's an endless loop to handle the message queue and then dispatch messages for callbacks to handle.

According to this https://stackoverflow.com/a/5193981/2290191, Handler and it's Looper run in the same thread.

If there is an endless loop on the main thread, wouldn't it block the entire UI system?

I know that I must be so silly to miss something. But it would be lovely for someone to reveal the secret behind this.

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

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

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}
Community
  • 1
  • 1
Ryan Hoo
  • 360
  • 7
  • 19

2 Answers2

7

Actually the Looper in the main thread is what allows drawing. When a view is invalidated, a message is passed to the main Looper telling it that a draw was requested. When the Looper processes that message, the actual drawing occurs. The reason other activity that holds up the UI thread holds up drawing is that it prevents the Looper from processing that draw message.

This is more or less how drawing works in any event based system, from Windows to Mac to Android.

Why not draw immediately instead of sending a message? Performance. Drawing is slow. If you do multiple changes in response to an event, you don't want to redraw the screen for each one. Doing it this way means you bunch all of your redraws for handling a single event into 1 redraw. For example if you set the text of 1 view and the image of another, they'll both be redrawn at the same time, and only once.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • I don't think you read my question carefully. I mean if there is `for(;;){ }` loop on main thread, how is that possible to send messages to draw the UI? – Ryan Hoo Mar 11 '16 at 06:05
  • 1
    I did, I don't think you understood my answer. One of the messages to the looper causes the draw. Draw is a special message sent to that looper. – Gabe Sechan Mar 11 '16 at 06:06
  • 3
    Or put another way- what you're thinking of as the main thread is actually that Looper processing messages and calling your code in response to it. That's a technique called an event loop or message loop, which is common in event driven programming. Here's a detailed explanation of Android's http://mattias.niklewski.com/2012/09/android_event_loop.html – Gabe Sechan Mar 11 '16 at 06:10
  • Thanks @Gabe Sechan that blog really explains everything. I used to figure all this out with a multi-thread model. Like the events must be sending from another thread and the main thread waits for events to handle. How foolish! – Ryan Hoo Mar 11 '16 at 06:37
  • @RyanHoo The Android system uses `epoll` to send messages between threads. When a message comes, `epoll` will wake up the thread, handling the message. You can check for more details in the source code `ActivityThread`, especially the `mH` Handler. – Jian Guo Feb 28 '17 at 03:41
  • @JianGuo, sorry may I have a question? Let's assume there is a ListView in the Activity, per the post says, in ActivityThread, the main UI looper.loop causes the main thread is waiting for the next message comes, my question is while user scroll up/down on the ListView, who send message to `ActivityThread.H` (or any other handler?)? Thanks in advance. – LiuWenbin_NO. Mar 27 '18 at 06:53
  • @GabeSechan, sorry I have to bother you. `Draw is a special message sent to that looper`, may I know what the message type is? And regarding `that looper`, do you mean the looper defined at `ActivityThread.H`? (If so, I think the message be processed at `ActivityThread.H.handleMessage`, I read the source code and see there are about 57 types of different messages, some lifecycle related are easy to understand like `PAUSE_ACTIVITY`, but I am still confused about what kind of message will be sent to this(or other?) handler). Thanks in advance. – LiuWenbin_NO. Mar 27 '18 at 06:57
  • @LiuWenbin_NO. I'm not sure about the scrolling process with the ListView, as my opinion it's more related to the drawing system. But for the question that who sends the message to `AndroidThread.H` specifically, this is a topic about the android IPC Binder design, there is a relationship between `ActivityManagerService`, `ActivityThread` and `ApplicationThread` (not sure about the class name, sorry for my bad memory) You can check more details on some blogs or the source code directly. – Jian Guo Mar 29 '18 at 04:16
  • @JianGuo, thanks very much for your reply, I will continue trying to find the exact answer, will post my findings here if I can find something. Thanks again. – LiuWenbin_NO. Mar 29 '18 at 06:04
2

This question is a delicate trap. Why infinite loops do not block UI threads because all UI threads behavior are starting from msg.next.

If there is no message, it means that no updates are required. All of our code is just a callback, such as Application onCreate, Activit onCreate, BroadcastReceiver onReceive.

All update callbacks are caused by the message, and these messages are from the system services, such as ActivityManagerService, InputManagerService, WindowMangerService. If you need to update the UI, the android service will send a message to the loop via the IPC.

So the infinite loop is infinite update.

Snow Albert
  • 547
  • 7
  • 15