【Handler Series 2 】 MessageQueue

preface

In the last article, we analyzed the Message in The Android Message mechanism. If you don’t know about Message, please move to the previous article Message. This article focuses on the MessageQueue MessageQueue used to store messages.

MessageQueue (MessageQueue) : as the name implies, message queues are used to store, retrieve, and remove messages. Instead of using a List or a Map, it uses Message’s internal linked List structure. So let’s study how MessageQueue stores, retrieves, and removes messages.

Parse enqueueMessage(Message MSG, long WHEN) to add the Message to the Message queue

First, this method is used to insert messages into our message queue, without further ado, to get directly to the code

boolean enqueueMessage(Message msg, 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) { ...... // omit some code msg.markinuse (); msg.when = when; Message p = mMessages; boolean needWake; / / current queue is empty or the when = = 0 (highest priority) or the message priority is higher than that in the first message queue priority if (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) Prev = p; // The next message object in the current queue p = p.ext; / / if there is no a message or message priority is higher than the current cycle to be inserted to the message, directly end cycle if (p = = null | | the when < p.w hen) {break; } if (needWake && p.isAsynchronous()) { needWake = false; }} // Insert the message between prev and p 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
  1. The code is pretty neat, and there are two main types of storage

  2. The first case is when the current message queue is empty or when=0 or when is smaller than the first message in the current queue. What does “when” mean? It is used to distinguish the priorities of messages. The smaller the message, the higher the priority.

    In this case, the current message is inserted directly into the first part of the message queue

  3. In the second case, which is normal, you need to loop through the current queue to insert the message into the middle of the queue (see code comments for details)

  4. Finally, native methods need to be called to wake up (Linux epoll, interested self-baidu). Here do not do too much explanation (in fact, the author is not very understand, hey hey hey hey)

Now that you understand the logic for adding messages to a queue, how do you get messages from a message queue

Parse next() to fetch the message from the queue

And you can guess, if you were to design a queue to fetch data, how would you do that? This one is easy, it must be a loop, and then return

So let’s look at where the return is in the next() method

Is a

final long ptr = mPtr;
if (ptr == 0) {
    return null;
}
Copy the code

Returns null when mPtr is 0. So what is mPtr? What does a value of zero mean?

MessageQueue constructor calls nativeInit() and returns the mPtr value. Dispose () sets mPtr to 0 and calls nativeDestroy(mPtr). When MessageQueue is released, set mPtr to 0. Therefore, it is understandable that null is returned when mPtr==0

Case 2

if (mQuitting) {
    dispose();
    return null;
}
Copy the code

When the message queue exits, null is returned and dispose method is called, then the queue is released.

Is three

int nextPollTimeoutMillis = 0; for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { 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; msg.markInUse(); return msg; NextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; }}}Copy the code

If MSG. Target (i.e. Handler) == null, then the synchronization barrier is enabled. If MSG. Target (i.e. Handler) == null, then the synchronization barrier is enabled. If the message’s WHEN is less than the current NOW, the MSG object is returned. Otherwise, it calculates how long it is until the current time is when and saves it to nextPollTimeoutMillis, then calls nativePollOnce() to delay wake up (Linux epoll, interested self-search), and after wake up, continues the loop to fetch message.

There are two problems:

  1. Why is MSG. Target == null an asynchronous message?
  2. When is MSG. Target assigned?

These two problems will not be dealt with for the time being, but we will decrypt them next time we write the sending processing message of the Handler

Parse removeMessages(Handler h, Runnable r, Object Object) to removeMessages from the message queue

RemoveMessages (Handler H, Runnable R, Object Object) as an example to analyze. Here we go, dick. Code it

Void removeMessages(Handler h, Runnable r, Object Object) { dddd if (h == null || r == null) { return; } synchronized (this) { Message p = mMessages; // Remove all messages at front. // Remove all messages at front = null && p.target == h && p.callback == r && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } // Remove all messages after front. = null) { Message n = p.next; if (n ! = null) { if (n.target == h && n.callback == r && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; }}}Copy the code

Analysis: Why is there a double loop here? If you know about linked lists, you know that linked lists are divided into lists of leading nodes and lists of non-leading nodes. Obviously, MessageQueue is a list of non-leading nodes. For a list without a lead, the first element needs to be processed separately before the rest of the list can be looped through as a lead. During the traversal of MessageQueue, nodes need to be deleted, so not only the first element needs to be processed separately, but also the first group of nodes that meet the deletion conditions.

The first while loop

After traversing the message queue of non-leading node that meets the preceding condition, the newly generated message queue P becomes the queue of leading node when the loop condition is not satisfied

Second while loop

Just like the normal traversal of the lead node, the condition is satisfied and the next message is removed to judge until there are no more messages

conclusion

Insert synchronization message

  1. Msg. target cannot be null
  2. Inserts into the message queue according to message priority when

Retrieves the message from the message queue

  1. When mPtr is 0, null is returned, indicating that the message queue was reclaimed
  2. Null is also returned when the message queue exits
  3. After the first message is taken out, judge now and when. If when

Removes the message from the message queue

  1. Firstly, the first message that does not meet the removal condition is removed in a circular way of linked list without leading node
  2. Secondly, the linked list of the leading nodes in the latter part is obtained, and the loop is removed to meet the conditions