Handler message mechanism is explained in detail, only this one, I will not forget it in the future

Handler message mechanism is explained in detail, only this one, I will not forget it in the future

The Handler message mechanism is a very important point of knowledge in Android. Before, many students on the Internet shared a lot of blogs introducing the handler mechanism, so why should I write another one? Because the blogs I read found that the analysis was dry and hard to remember. So, this time I took the time to make a timing diagram of the message mechanism, in order to see the picture for the right brain to remember, and never forget it! ! !

Ok, let s not talk too much nonsense, first look at the timing diagram, I will explain the collusion based on the diagram below:


Where do you start? When you analyze the Handler message mechanism, you must either analyze it from sendMessage() or from Looper.prepare(). I like to start with the latter, because the premise of the execution of sendMessage() is that Looper calls the loop() method, otherwise it will be closed. No news. Let's start with Looper first, because after Looper executes the loop method, the message can be received normally and subsequent operations can be performed.

Main thread Looper initialization

Let s take a look at the Looper object in the figure. The first control focus of the Looper object is the ActivityThread creation, initialization of the Looper, new MessageQueue, or Queue focus in the figure above. From this focus we can see that we are talking about the main thread Looper, so the Looper object in the figure above is the main thread. The sub-thread is actually similar, that is, Looper's initialization needs to be called by ourselves. Let's look at the main thread first.
When was Looper in the main thread created? Because our code did not create a Looper for the UI thread, the system actually created the Looper of the UI thread for us when the application was created. Look at the code:

public static void main(String[] args) {
   //...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
   //...
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
 

This is the main() method of ActivityThread, which can be considered as the entry point of the application creation. In the main() method, we first call the Looper.prepareMainLooper() method. After preparing the Looper, we finally call Looper.loop(). At this time Looper of the main thread (UI thread, not explained later) starts the loop, which means that we can send messages to the main thread.

Looper.prepareMainLooper() still calls the prepare() method internally, setting up a Looper instance in the ThreadLocal object, as follows:

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));
}
 

Let's look at Looper's construction method:

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

A MessageQueue object is created in the construction method. As the name suggests, it is the queue used to put the Message. After Looper calls the loop() method, it starts to loop, and it will fetch messages from this MessageQueue. If there is a fetch, there must be a release. After reading the initialization of Looper, the Looper loop is also running. So let's look at sending messages next.

Handler sends a message

If you need to send a message, you must create a Handler, so I added an Activity object in the picture, and called the Handler construction method to create the Handler object. There is one point in the Handler construction method that needs attention:

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
   //1
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
   //2
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
 

Creating a Handler will eventually call the above construction method, except of course the Handler that creates a child thread to receive messages. The Looper object is obtained through Looper.myLooper() at label 1. The myLooper method is still taken from ThreadLocal, so of course the Looper of the main thread is obtained. Get Looper's MessageQueue at marked 2. As long as we get this MessageQueue, we can put the Message in the MessageQueue of the main thread in the Handler.

Okay, we can finally start sending messages

Look at the first focus of the Thread object in the picture. This Thread object is to simulate a worker thread. The first focus calls the Message.obtain() method to obtain a msg, and then calls the sendMessage() method of the Handler (I will just Don t post the source code, because the lifeline and focus in the figure are the timing of the source method call, you can check the source code when you look at it), sendMessage() internally calls the Handler s sendMessageDelayed() method to reach the next focus, and the sendMessageDelayed() method is called again After the sendMessageAtTime() method inside the Handler, the sendMessageAtTime() method then calls the enqueueMessage() method of the Handler itself. I want to post the code for enqueueMessage(), because there is a wonderful place here:

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

The msg we sent here points its target attribute to the Handler itself. Why do you want to do this? It is for Looper to call back the Handler's callback after receiving the message. Later, when we send the message to Looper, we will elaborate on it. Keep watching, enqueueMessage The () method calls the queue.enqueueMessage(msg, uptimeMillis) method. This queue is the MessageQueue when we create the Handler. Let s look at the enqueueMessage() method of MessageQueue:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
       //...
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
       //1
        if (p == null || when == 0 || when < p.when) {
           //New head, wake up the event queue if blocked.
            msg.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;
           //2
            for (;;) {
                prev = p;
                p = p.next;
               //3
                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;
}
 

The code is quite long, in fact, it is the enqueue operation. Here is mMessages, and p points to mMessage. We can understand it as a message queue to be executed. The queue is sorted according to the time of when and the first message is Execute first.
We look at label 1, there are three conditions, if the mMessages object is empty, or when is 0, it is executed immediately, or the when time of the new message is earlier than the when time of the mMessages queue, and the new msg is met if the above condition is met. Insert to the front of mMessages and point next to it, that is, msg will be inserted into the front of the queue in the above figure, waiting for the loop to poll.
If the above conditions are not met, enter the else code, we can see that there is a for loop in the label 2 to traverse the existing message object, and there is an if statement in the label 3 when <p.when when is a new message The execution time of the message, p.when is the execution time of the message in the queue. If you find a message that is executed later than the new message, execute msg.next = p; prev.next = msg; that is, insert the message Prior to the implementation of new messages. Well, the message we sent here is in the queue, and the message has been sent. Now we just wait for Looper to loop to our message.

Looper loop

After Looper calls the loop() method, it is an endless loop. The following is the source code of the loop() method:

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 (;;) {
       //1
        Message msg = queue.next();//might block
        if (msg == null) {
           //No message indicates that the message queue is quitting.
            return;
        }

       //...
        
        try {
           //2
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       //...
    }
}

 

The code is very long, I only post the useful ones. You can see that at label 1, msg will be taken out of the queue, and then at label 2, the dispatchMessage(msg) method of the target attribute of msg will be called. First of all, we mentioned this target. One moment, in the enqueueMessage() method of the Handler, we assign the Handler itself to the target attribute of msg, then the call to the label 2 in the loop loop will return to the dispatchMessage(msg) method of the Handler.

Distribute messages

dispatchMessage(msg) is the processing of dispatching messages, we look at the source code:

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

If the callback of msg itself is not empty, call the callback method of msg itself, otherwise enter the else inside the
else to determine whether mCallback is empty. This mCallback can be passed in the Handler construction method. If mCallback is not empty, call the mCallback callback , Otherwise call the handleMessage(msg) method. At this callback, the message has been received, and the following is our own business logic processing.

My purpose of writing this blog is actually the flowchart above. If you understand the flowchart, you will naturally remember it in the future.