Handler: Post, Send, Callback, handleMessage, etc. How does it manage messages and notify the corresponding threads?

Handler, Looper, Message, and MessageQueue are the four main members of the Handler mechanism. They are also responsible for Message management and notification.

Members of the note
Handler Handler is used to send and process messages
Message The carrier of messages in the overall communication mechanism
MessageQueue Each Looper will hold a MessageQueue. As a MessageQueue, messages sent by the Handler will be put into the MessageQueue. Messages in the MessageQueue will be arranged according to the length of when
Looper Each thread can have only one Looper. The Looper for the main thread is created in the Main () of the ActivityThread. Child threads need to be created manually

We can start with the last enqueueMessage method from the previous article:

// sendMessageAtTime() 使用的是 enqueueMessage()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // The mQueue is held by the Handler's mLooper and is referenced in the constructor
    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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // Call enqueueMessage() for MessageQueue
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

All messages sent by the Handler are put into the MessageQueue. How does the MessageQueue hold the Message? MessageQueue is actually a linked list structure, holding a mMessage as the head node, while Message has a next node. A string of messages forms a MessageQueue. Let’s look at how MessageQueue stores messages:

boolean enqueueMessage(Message msg, long when) {...synchronized (this) {...// assign delay time when to MSG
            msg.markInUse();
            msg.when = when;
            // Get the Message of the current header
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // If there are no headers or the delay is 0, or the delay is less than the delay of the header, the new Message is put as the header
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // If there are more messages in the MessageQueue, or if the inserted Message's WHEN is not 0, the list is traversed, and the Message is inserted at the corresponding position according to the length of when
                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

We can see that the logic of storing messages is not very complicated, that is, if there is no Message in the MessageQueue, the new Message is used as the header, and if there is a Message in the MessageQueue, the short to long Message is inserted according to the when of each Message.

After putting the Message, how to retrieve it? This is where Looper comes in. We know that to use Handler in the child thread, we need to actively create Looper and call looper.loop (), Looper.loop() is the process of retrieving messages from MessageQueue and sending them to Handler’s dispatchMessage() :

public static void loop(a) {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        finalMessageQueue queue = me.mQueue; .for (;;) {
            // Go through the for loop and fetch the Message from MessageQueue
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return; }...try {
                // This is then passed to the Handler's dispatchMessage. When sending a non-barrier Message using the Handler, the Handler assigns this to the target of the Messagemsg.target.dispatchMessage(msg); . }catch (Exception exception) {
                ...
            } finally{... }... msg.recycleUnchecked(); }Copy the code

How does MessageQueue fetch Message via next()?

Message next(a) {
        // keep iterating through the for loop
        for (;;) {

            // Sleep until the next MSG when, insert message will wake up
            nativePollOnce(ptr, nextPollTimeoutMillis);
          
            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) {
                    // MSG's target is Handler, if empty, it is a synchronization barrier, and the while loop finds the first asynchronous message
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! =null && !msg.isAsynchronous());
                }
                If there is a memory barrier, the message is now the first asynchronous message, otherwise it is the first message
                if(msg ! =null) {
                    if (now < msg.when) {
                        For (;;); To start, sleep until MSG is ready
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Returns the first message as normal, with the header pointer pointing to the next 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();
                        // Returns a message
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1; }... }}Copy the code

We see how the message is fetched and we know that the message is sent to Handler’s dispatchMessage() method. So what does dispatchMessage() do?

public void dispatchMessage(@NonNull Message msg) {
    if(msg.callback ! =null) {
        // message.callback.run()
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            // If you override the handleMessage of the Handler's callback and return true, just pass the handleMessage of the callback. Otherwise the handleMessage of the Handler and callback will go
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code

This is how we communicate with Handler, but we are not done with Handler. For example, memory barriers, asynchronous messaging, Looper creation and access, and ThreadLocal are mentioned above.

Update is too slow, too lazy, come home from work at 11 o ‘clock every day, do not want to read, do not want to write… Try to keep the record going