This is the second article in the “Android messaging” series that follows:

  1. You must have eaten sushi! — Android messaging mechanism (construction)
  2. You must have eaten sushi! — Android Messaging (distribution)
  3. You must have eaten sushi! — Android messaging (processing)

The story of the messaging mechanism

Sushi Chen is placed on sushi plates, which are lined up on a conveyor belt in sequence. After the conveyor belt is activated, the sushi is presented to you one by one, and you have three ways to enjoy it.

Bringing Android concepts in, it becomes the story of Android messaging:

  • Sushi dishes — – >Message
  • The queue – >MessageQueue (MessageQueue)
  • Conveyor belt — – >Message pump (Looper)
  • Sushi –> Data you care about
  • How to enjoy sushi –> How to process data

Temporarily not foundHandlerThe corresponding entity in this scenario. It’s a more abstract concept that produces sushi, sends it to the conveyor belt, and defines how to eat it. Let’s call it thetaMessage handler.

If you want to open your own revolving sushi restaurant, the following questions are key:

  1. How to produce sushi (how to structure messages)
  2. How to Distribute sushi (How to distribute messages)

Information on how to structure messages can be moved to a blog turn sushi you must have eaten! Android messaging mechanism (construction). This article analyzes “how to distribute messages” from the source point of view.

The distribution problem is how to get sushi from the chef to the consumer. The rotating sushi system works by placing the sushi one by one on a conveyor belt, which then rolls around. In Android messaging system, there are two similar steps: 1. Message queuing 2. The message pump

(PS: the bold italics in the following words represent the inner play to guide the source code reading)

1. Message entry

There are two basic questions to ask about joining the team :(1) when and (2) how. The second question is really asking “What is the data structure of the message queue?” . Specific data structures correspond to specific insert methods. Knowing nothing about message queues, I have no clue where to start reading the source code. We can even imagine (a powerful human skill) when we don’t have one. With the memory of the data structure, I have a vague idea that “queuing” should be the basic operation provided by queues, so LET’s start with MessageQueue

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link"Messages are not added directly to a MessageQueue, * but rather through {@link Handler} objects associated with the Looper.”
 * <p>
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */
public final class MessageQueue
{... }Copy the code

The comment provides the key hint that “messages are not added directly to the message queue, but through a Handler object.” Instead of rushing to see the Handler, check MessageQueue to see if there is a queue entry operation.

// Omitted some non-critical code
boolean enqueueMessage(Message msg,
                           long when)
    {...synchronized (this) {... msg.markInUse(); msg.when = when;//p points to the message queue header
            Message p = mMessages;
            boolean needWake;
            // Push the message to the head of the queue
            if (p == null || when == 0 || when < p.when)
            {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            }
            // Insert the message into the queue
            else{...// Start at the head of the message queue to find the right place to insert the message
                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.nextprev.next = msg; }}...return true;
    }
Copy the code
  • Sure enough, there is an enqueue function, and the data structure of the message queue is exactly the same as that of the message pool (an introduction to message pools can be found here), both linked lists.
  • You see here that the second problem is basically solved: the message is queued by an insert to the list. Let’s think a little bit deeper:Where is the new message inserted into the linked list?You can see that there is a big if-else in the source code, and the criterion is when. Search up the call chain and find the following function in the Handler:
    /** * "Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) The < var > uptimeMillis < / var >." * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     * 
     * @paramUptimeMillis "The absolute time at which The message should be * delivered, using The * {@linkAndroid. OS. SystemClock# uptimeMillis} time - base. "*... * /
    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
  • The quote in the comment is critical: “Insert the message into the message queue and queue at alluptimeMillisAfter the previously generated message “.uptimeMillisIndicates the time when the message was sent. In that sense,Messages are arranged in chronological order, with the oldest at the head of the queue and the most recent at the bottom. The message into the team is divided into two situations: 1. Tail insertion 2. Middle insertion. The tail insert indicates that the latest message is inserted at the end of the queue. Go back and watch it againMessageQueue.enqueueMessage()That big if-else does both.
  • fromHandler.sendMessageAtTime()Searching further up the call chain leads to the familiar method:
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
Copy the code
  • That’s what we use to send messagesHandler.sendMessage()! At this point, let’s sum up the “message in the team” :The Handler sends messages by inserting them in chronological order into a message queue, which is a linked list structure with the oldest message in the header and the latest message in the tail

2. The message pump

The sushi has been arranged in chronological order, and it’s time to press the button to start the conveyor belt and get the sushi going. For The Android messaging mechanism, making a message loop means constantly fetching messages from a message queue. MessageQueue has a queue entry operation, so there must be a queue exit operation:

// Omit a lot of non-critical code
   Message next(a) {...for(;;) {...synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // MSG points to the head of the message queue
                Message msg = mMessages;
                // Find the first synchronous message (there are also synchronous asynchronous messages, let's ignore this detail for now)
                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) {
                    // The oldest message in the message queue will be distributed in the future, not yet, and so on
                    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;
                        // The first synchronization message is in the middle of the message queue
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        // The first synchronized message is at the head of the message queue, which points to its next message (usually this way)
                        } else {
                            mMessages = msg.next;
                        }
                        // Disconnects the found synchronization message from the message queue
                        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
  • This function is long and omits details that are not relevant to the topic, such as queue idle waiting and asynchronous messages. After removing these special cases, the dequeuing operation is to fetch the header of the message queue (the header of the queue is the oldest message, and the tail of the queue is the latest message). This conforms to the first-in-first-out nature of the queue, and the earlier the message is distributed first.
  • There must be a loop that keeps callingMessageQueue.next()Continuously fetch messages from the message queue for distributionAfter a search, sure enough inLooperFound in:
    public static void loop(a)
    {
        // Get the message pump for the current thread
        final Looper me = myLooper();
        if (me == null)
        {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // Get the message queue of the current thread
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        //nandian
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Take an infinite loop of messages
        for(; ;) {// Retrieve message from queue head (possibly blocked)
            Message msg = queue.next(); // might block
            // Exit the loop if there is no message
            if (msg == null)
            {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if(logging ! =null)
            {
                logging.println(">>>>> Dispatching to " + msg.target + "" +
                                        msg.callback + ":" + msg.what);
            }

            // Distribute the message
            msg.target.dispatchMessage(msg);

            if(logging ! =null)
            {
                logging.println("<<<<< Finished to " + msg.target + "" + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // Error of identity of the thread wasn't.
            //nandian
            final long newIdent = Binder.clearCallingIdentity();
            if(ident ! = newIdent) { Log.wtf(TAG,"Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass()
                                                                                                                                                                        .getName() + "" + msg.callback + " what=" + msg.what);
            }

            // Retrieve the message
            msg.recycleUnchecked();
        }
Copy the code
  • This function is the endpoint of the Android messaging mechanism for constructing and distributing messages, and the starting point for processing messages. Looper retrieves the oldest message from the message queue through an infinite loop, distributes it to the corresponding message handler, and finally reclaims the message. So far, the last article you must have eaten sushi! The puzzle left by the Android messaging mechanism (construction) is solved: messages are recycled as soon as they are distributed.
  • When there are no messages in the message queue,queue.next()Blocks the current thread. You don’t have to worry anymoreLooper.loop()An infinite loop consumes CPU resources.
  • When will looper.loop () be called?We all know that the main thread comes with a Looper, and while that’s a good place to start, there’s too much going on that’s irrelevant. So to move to a purer entry point,HandlerThread:
/** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    @Override
    public void run(a) {
        mTid = Process.myTid();
        / / 1. Ready to stars
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //2. Looper starts the loop
        Looper.loop();
        mTid = -1; }}Copy the code
  • Again, the comment gives us a lot of hints: “This class is used to create a class withLooperThe thread.Don’t all threads have itLooper? (Think about it: threads are a Java concept,LooperIs an Android concept). So what do we need to do specifically when the thread starts to get a stringLooperThe thread. inThread.run()See two key methods, among whichLooper.loop()It’s been analyzed. There’s another one before itLooper.prepare(), click inside to see:
/**
  * “Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.”
  */
public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    final MessageQueue mQueue;

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare(a) {
        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(newLooper(quitAllowed)); }}Copy the code
  • The quoted comments reveal a key truth: Looper is used to create a message loop for a thread that, by default, has no message loop associated with it. A message loop can be started by calling looper.prepare () in the thread, and then looper.prepare () to loop through the message.
  • inprepare()In, create a newLooperInstance, and set toThreadLocalObject, which is used to ensure a one-to-one relationship between thread objects and objects of custom type. This is a big topic, I won’t go into it. Now just knowLooperIt binds an instance of itself to a thread, i.e. only one threadLooperObject. soThe Android messaging system hierarchy looks like this: 1ThreadCorresponding to aLooper1,LooperoneMessageQueue1,MessageQueueThere are severalMessage.

conclusion

When sending a message, the message is inserted into the message queue in chronological order, and Looper iterates through the message queue to retrieve the message and distribute it to the corresponding Handler

This is not the end of the story. The next article will cover processing messages.