Handler Looper MessageQueue Principle Analysis

When it comes to communication between Andorid threads, the most common is the Handler. The Handler principle is very important. Handler, Looper, and MessageQueue are discussed from a source code perspective

1. Start with Looper

A Looper is a wheel.

Looper’s source code is small, less than 300 lines and mostly comments. Let’s go straight to the code:

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

Copy the code

The Looper constructor creates a MessageQueue and stores the current thread information.

public static void prepare() { prepare(true); } 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)); }... Public T get() {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) return (T)e.value; } return setInitialValue(); }Copy the code

After the prepare method is called, the ThreadLocal key is used to fetch the Looper information. If so, an exception is thrown. Prove that only one Looper is allowed per thread.

If it is not fetched from a ThreadLocal, a new Looper of that new is stored in the ThreadLocal, and the Looper is bound to the current thread.

The prepare method is concise, so let’s look at the main Looper method: looper.loop () :

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; . for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }... try { msg.target.dispatchMessage(msg); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked(); }}Copy the code

As you can see, there’s an infinite loop at first to prevent the program from terminating and exiting. At the entry to the loop a message is fetched from MessageQueue, the queue.next() method.

Take a message, if successful, will call MSG. Target. DispatchMessage (MSG).

The msg.target here is the handler, which will be available later.

Ok, that’s the main Looper source code, very little code, easy to understand.

Next up is MessageQueue

2.MessageQueue

We just saw that Looper’s loop() method called Queue.next (), which blocks. So let’s start with MessageQueue’s next() method.

Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) {... synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis();  Message prevMsg = null; Message msg = mMessages; if (msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg =  msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } // if (MSG! = null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }... }... }}Copy the code

This code is quite informative, but when broken down, we can determine that messagequyue.next () calls the next variable assignment of Message, which is a singly linked list structure where the next attribute points to the address of the next Message.

That, when mMessages have value, which will call to MSG. The target. The dispatchMessage ().

So when do YOU assign mMessages?

The answer is in the next very important method: enqueueMessage()

boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { ... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; / / key here if (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, wake up the event queue if blocked. MSG. The next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is  a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! = 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }Copy the code

Message is passed in by the enqueueMessage() method, within which mMessages are assigned. And then into the next () method out of circulation, and inform the stars () call MSG. Target. DispatchMessage ().

The MSG. When parameter is the delay passed in when we call handler.sendMessagedelay (). The specific time cycle calculation method can be seen in next(), which will not be explained too much here.

The process is clear. Whoever calls the enqueueMessage method of MessageQueue sends a Message to Looper, which in turn sends it to Handler’s Dispatch () method.

3.Handler

The answer, of course, is the enqueueMessage() method of MessageQueue called by the Handler.

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

Copy the code

This code is just an example of how all Handler post and sendMessage methods end up calling sendMessageAtTime().

Next look at sendMessageAtTime() :

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

Copy the code

SendeMessageAtTime () eventually calls Handler’s enqueueMessage().

As a result, Hanlder’s enqueueMessage() must call MessageQueue’s enqueueMessage() :

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

Copy the code

Sure enough, finally we look at Handler’s dispathMessage() method:

public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code

The whole closed-loop process is clearly visible, and the MSG is finally sent through the dispatchMessage() to the common hanldeMessage().

Finally, for your convenience, I use a diagram to illustrate the whole call process: