Android message mechanism as one of the mechanisms of system operation, in a larger factory interview was asked the probability is relatively large, visible its importance. The following two parts introduce the overall mechanics of messages and then talk about the types of messages.

1. Message mechanism

In the messaging mechanism, there are the following roles:

  • A. Message: Message entity
  • B. MessageQueue: Message queue, which stores messages in a linked list
  • C. Looper: Loops through the MessageQueue to obtain the Message and send it to the Handler
  • D. Handler: Processes Message

How do they work together from the source point of view

Stars:
public final class Looper {
    ...

    /** 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() {
        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(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    ...
}
Copy the code

As you can see from the source code above, Looper provides two methods to create Looper objects and store the created objects in sThreadLocal. As you can see from the prepare method, each thread is allowed to create only one Looper object, otherwise an exception will be thrown. Instead of creating a Looper for the main thread, where is the Looper for the main thread created? PrepareMainLooper () method prepareMainLooper() method prepareMainLooper()

Initialize the current thread as a looper, marking it as an application's main looper.
The main looper for your application is created by the Android environment, so 
you should never need to call this function yourself.  See also: {@link #prepare()}
Copy the code

Initialize the current thread as a Looper, marking it as the main loop of the application. Your application’s main circulator is created by the Android environment and should never call this function itself. The ActivityThread main method is used to create a Looper.

public static void main(String[] args) { ... Looper.prepareMainLooper(); . Looper.loop(); }Copy the code

Confirms the above notes. Let’s look at the loop() method inside:

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { ... for (;;) Message MSG = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; }... Try {/ / target is Handler, by a Handler for processing, specific see below the Handler parsed MSG. The target. The dispatchMessage (MSG); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; . }}Copy the code
Handler:

Let’s start with the SEND family of methods:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
Copy the code
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
Copy the code
    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

It can be seen that the sendMessageAtTime method was called at the end. Other methods including the POST series also called sendMessageAtTime. The difference is that the uptimeMillis was passed in at the end. Then look at Looper calling Handler’s dispatchMessage method

Public void dispatchMessage(MSG) {if (MSG. Callback! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code
MessageQueue:

Next look at the enqueueMessage method in sendMessageAtTime, which enqueues a message. Enter the MessageQueue class

// MSG - the above message entity, Boolean enqueueMessage(Message MSG, Synchronized (this) {// synchronized (this) {// synchronized (this) { If (McOntract) {IllegalStateException e = new IllegalStateException(msg.target + "sending message to A)  Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; // mMessages to the queue header Boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, // If the queue is empty or the MSG does not need to be delayed, or the MSG needs to be delayed for a longer time than the queue header message, // then the MSG is inserted into the header MSG. Next = p; mMessages = msg; NeedWake = mBlocked; mBlocked indicates whether the message queue is currently blocked. NeedWake = mBlocked; } else {// Insert MSG in the middle of queue, insert position // according to when, when small MSG will be first // this is easy to ensure that the first queue MSG will be executed first, MSG needWake = mBlocked && p.target == null && msg.isAsynchronous(); 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.next prev.next = msg; } // When the MSG is fetched at next(), it is assumed that when is greater than 0, so the MSG needs to be delayed until the MSG is executed, so mBlocked=true. If (needWake) {nativeWake(mPtr); if (needWake) {nativeWake(mPtr); if (needWake) {nativeWake(mPtr); } } return true; }Copy the code

Next ():

PendingIdleHandlerCount = -1 int pendingIdleHandlerCount = -1 int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; // Time to block for (;;) {// in an infinite loop, get MSG if (nextPollTimeoutMillis! = 0) { Binder.flushPendingCommands(); } // Call a local method to block and suspend nativePollOnce(PTR, nextPollTimeoutMillis) before the header message is executed; 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) {// determine whether a message has its own barrier // its own 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) {if (now < msg.when) {if (now < msg.when) {if (now < msg.when) { NextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); } else {// When a message is executed, set the queue to a non-blocking state, and return mBlocked = false; if (prevMsg ! Prevmsg.next = msg.next; prevmsg.next = msg.next; } else {// otherwise, direct mMessages to the next message mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; NextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // The first time through the loop (the message queue is empty or the trigger time of the first message in the message queue has not reached), / / show idle / / get IdleHandler number if (pendingIdleHandlerCount < 0 && (mMessages = = null | | now < mMessages. When)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) {// There is no IdleHandler to run, loop and wait for mBlocked = true; // Set the blocking state to true mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the IdleHandler, only the first loop will run for (int I = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); PendingIdleHandlerCount = pendingIdleHandlerCount = pendingIdleHandlerCount = 0 PendingIdleHandlerCount < 0 = pendingIdleHandlerCount < 0 = pendingIdleHandlerCount < 0 = pendingIdleHandlerCount < 0 pendingIdleHandlerCount = 0; NextPollTimeoutMillis is set to 0, indicating that no block is required. Check the message queue again. nextPollTimeoutMillis = 0; }}Copy the code
After the above two methods, here is a summary of the overall flow of message mechanism:

When a message joins the queue (that is, when a new message joins the queue), it finds an appropriate position to join the queue according to the delay time, so as to ensure that the whole queue is in ascending order with the delay time.

  • A. When the delay time of the queue entry message is shorter than the original queue header message, or the queue is empty, the message will join the queue at the queue head, and when the list is blocked, the queue will wake up;
  • B. Otherwise, the position to join the team is not the head of the team.

This feature enables the queue header message to be sent directly when the queue is sent, which is the message that needs to be executed first. When the team

  • A. If the queue is empty, it will block indefinitely;
  • B. If the queue exit message reaches the execution time, the queue exit;
  • C. The outgoing message is blocked before the execution time.

Ii. Message types (Synchronous message, asynchronous message, message barrier)

The Handler mechanism has been described above. Message barriers and asynchronous messages are mentioned in the code comments.

There are three types of Hander messages: synchronous messages, asynchronous messages, and message barriers. Let’s take a look at how they are used.

1. Synchronize messages

Most of the messages we send using Handler are synchronous messages, such as the following code:

Handler handler = new Handler(Looper.getMainLooper()); Handler. post(new Runnable() {@override public void run() {// do something}}); // Handler. SendEmptyMessage (0); Message MSG = message.obtain (); msg.what = 1; handler.sendMessage(msg); .Copy the code
2. Asynchronous messages

The usage is as follows:

Message msg = Message.obtain(); msg.what = 2; msg.setAsynchronous(true); // Set the message to asynchronous message handler.sendMessage(MSG);Copy the code
3. Message barriers

The API for adding message barriers is as follows:

    /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * @return A token that uniquely identifies the barrier.  This token must be
     * @hide
     */
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }
Copy the code

After adding the message barrier, the token of the message will be returned. When removing the message barrier, the token will be used. The specific API is as follows (also requires reflection call) :

    /**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    public void removeSyncBarrier(int token) {
    }
Copy the code

This gives you a general idea of how to use the three message types. What are they for, or what are the differences?

MessageQueue#next() :

Message next() { for (;;) { synchronized (this) { Message prevMsg = null; Message msg = mMessages; if (msg ! = null &&msg. target == null) {do {prevMsg = MSG; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) { if (now < msg.when) { } else { if (prevMsg ! Prevmsg.next = msg.next; prevmsg.next = msg.next; } else {// otherwise, direct mMessages to the next message mMessages = msg.next; } } } } } }Copy the code

As you can see from the above code, the message barrier blocks synchronous messages behind the message queue, while asynchronous messages are not affected by the message barrier. In the absence of message barriers, synchronous and asynchronous messages are essentially indistinguishable.

Reprint please indicate the source: www.jianshu.com/p/9ecccd6d4…