How does the Handler send messages to MessageQueue? What is MessageQueue?

Recently, I have been reviewing the knowledge of Android, and I have been reading the source code of Handler for the past two weeks. In order to deepen my understanding, I decided to record it in the form of blog. Handler has been used by all of us, and its usage is not analyzed here. Everyone knows that Handler is used to switch messages between threads in Android, but how does it do it? Why would creating a handler object directly from a child thread crash? Why, and for what reason, do Android developers not allow UI updates in child threads? With these questions IN mind I walk through the block of messages that handler sends.

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler();
        Message message = Message.obtain();
        message.obj = "Message created from child thread";
        handler.sendMessage(message);
        Looper.loop();
    }
}).start();
Copy the code

Forget the Looper part for now, this section focuses on handler.sendMessage(message); This line of code exactly what it does, click on the code to follow in to see.

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

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

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if(queue == null) {// omit...return false;
    }
    returnenqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; // omit...return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Handler sends messages according to the order sendMessage, sendMessageDelayed, sendMessageAtTime. And finally the enqueueMessage method of MessageQueue is called to perform the insertion of the message. In sendMessageAtTime, we first see the concept of MessageQueue, which is assigned by mQueue. MQueue is via mlooper.mqueue; That is, the Looper object maintains a MessageQueue, which will be discussed later. Next, to see how messages are inserted into message queues, I copy MessageQueue’s enqueueMessage method to the following.

Boolean enqueueMessage(Message MSG, long WHEN) {// If the target attribute of Message is null, throw IllegalArgumentException, // enqueueMessage (MSG. Target = this; Each Message object maintains a handler objectif (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target."); } // An exception will be thrown if the message is already marked as used. The same message cannot be sent twiceif (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) {// use msg.markinuse (); // Assign a value to the when variable of message, which is the user-defined delay MSG. P = null Message p = mMessages; boolean needWake; P == null; p == null; p == nullifThe value of when is systemclock. uptimeMillis()+0if(p = = null | | the when = = 0 | | the when < p.w hen) {/ / MessageQueue is singly linked list is used to implement a queue, where the current message nodes subsequent next to null MSG. Next = p; // Assign mMessages to mMessages = MSG; needWake = mBlocked; }else{// Suppose that when you send the message for the second time and the value of when > message01. when, since mMessages were assigned to the first message object (here I refer to Message01), // When you send Message02 (when > message01.when), p = Message01, so when > p. hen, the condition is not validelse// Define Message prev, where prev=null Message prev; / / openforThe loop sorts messages by the value of the WHEN parameter as a conditionfor(;;) { //prev = message01 prev = p; //p = null p = p.next; // The condition permits the exit of the loopif (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false; } } //message02.next = null msg.next = p; // Message01. Next = message02 // I use message01 and message02 as examples to analyze what the handler does when sending a message. MessageQueue is a single linked list. EnqueueMessage's execution logic is to sort messages according to the when parameter (from small to large). Prev. Next = MSG; }if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code

So I’m going to use handler to send four messages, and let’s see how they’re sorted, and I’m going to follow the code step by step in a graphic way, okay

handler.sendMessage(message01); / / step 1 when = 0 handler. SendMessageDelayed (message03, 2000 l); . / / step 2 when actual = SystemClock uptimeMillis () + 2000 l, convenient here will understand the when to 2000, under the same handler. SendMessageDelayed (message04, 500 l); / / step 3Copy the code

Topic answer

Why would creating a handler object directly from a child thread crash

Handler handler handler handler handler handler handler handler handler handler handler handler

Public Handler(Callback Callback, Boolean async) {// omit some code... mLooper = Looper.myLooper();if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()"); } // omit some code... }Copy the code

There’s a Looper. MyLooper (); Fetching the Looper object of the current thread from ThreadLocal will result in a RuntimeException because the child thread did not initialize the Looper and save it to ThreadLocal. This is why looper.prepare () needs to be executed in child threads before handler initialization; Now, you can click on this code to have a look at the source code, here is a brief talk, in the next summary of Looper I will follow the source code specific explanation.

Why, and for what reason, do Android developers not allow UI updates in child threads?

Is there a problem with multi-threaded concurrency if YOU allow UI updates in child threads? How can a control be thread-safe if it can update its UI in different threads? Let’s assume that locking is necessary for this reason. Locking is expensive and complicates the code, so it’s better to just allow UI updates in the main thread.