Android Messaging Mechanism

The message mechanism of Android is also the operation mechanism of Handler. When Handler runs, it needs the support of Looper and MessageQueue at the bottom level. Handler serves as the upper interface. So for the most part, we only need to interact with handlers, and there is not much Looper and MessageQueue involved. The following will be from the perspective of use combined with source code analysis Handler operation mechanism.

Usually we mainly use Handler for thread communication, such as: the child thread to the main thread to send messages, send messages between child threads, the following code to complete the two child thread to send messages:

Handler mHandler = null;

private void test(a) {
    new Thread(new Runnable() {
        @Override
        public void run(a) {
            for (int index = 0; index < 5; index++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Message message = Message.obtain();
                message.what = index;
                mHandler.sendMessage(message);
            }
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run(a) {
            Log.i("zx"."Thread name:" + Thread.currentThread().getName());
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    Log.i("zx"."msg.what="+ msg.what); }}; Looper.loop(); } }).start(); }Copy the code

The print result is as follows:

msg.what=0
msg.what=1
msg.what=2
msg.what=3
msg.what=4
Copy the code

The top thread sends a message to the bottom thread every second, and the bottom thread automatically calls handleMessage() every second and receives the message. What is the logic inside sendMessage() that causes handleMessage() in another thread to be called automatically? What’s looper.prepare ()? Why don’t we write that when we create a Handler in the main thread? With these questions in mind, we jumped into the source code to find out. Let’s start with the Handler constructor.

public Handler(a) {
    this(null.false);
}

public Handler(@Nullable Callback callback, boolean async) {
    // Omit the above code
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()

                    + " that has not called Looper.prepare()");

    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

The constructor first gets looper, then checks to see if looper is null. If looper is null, it throws an exception. When is looper null, or when was looper initialized? Looper = new Looper; Looper = new Looper; Looper = new Looper; Looper = new Looper;

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(new Looper(quitAllowed));
}
Copy the code

Looper is initialized in prepare and set to a ThreadLocal. ThreadLocal can be simply understood as an independent storage area for each thread, each thread has a copy, and access to the scope of the thread, each thread can only access its own data stored in the ThreadLocal, more knowledge and source code for ThreadLocal, please refer to my article. So if looper.prepare () is first followed by new Handler(), since Looper is obtained in the Handler constructor in the same thread, it will not throw an exception. Otherwise, it will certainly throw an exception. As for what this Looper is, let’s read on.

Next, trace the Handler’s sendMessage to see what logic is inside sendMessage. Post the source code for sendMessage:

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

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

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;/ / comment 1
    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);/ / comment 2
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;/ / comment 3
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);/ / comment 4
}
Copy the code

Note 2: sendMessage finally calls Handler’s enqueueMessage, which wraps Message once. The line in comment 3 points the target of the Message to the Handler itself. This is important, but we’ll talk about it later. The line in comment 4 shows that the enqueueMessage of queue is called. From comment 1 we know that queue is Handler’s mQueue, and this mQueue is Looper’s mQueue(see Handler construction). This mQueue is an instance of the MessageQueue class. That is, the Handler’s sendMessage ends up calling the enqueueMessage() of its Looper’s MessageQueue, which is presumably a MessageQueue. EnqueueMessage is an enqueue operation. Let’s see if the enqueueMessage source code is what we think it is

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // Omit part of the code
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        // Key parts
        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;
            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;
        }

        // We can assume mPtr ! = 0 because mQuitting is false.
        if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code

The source code is a little longer, so you can just look at the key parts, and it does insert messages into the queue as we thought, but here we use a first-in, first-out queue implemented by a single linked list (p.ext in the source code, etc.).

Since sendMessage() inserts messages into the queue, when does it take them out? Who takes it out? At present, we have encountered Handler, Looper, MessageQueue three classes, the source code is very long, a little see, afraid is to halo around. Let’s look at who called the Handler’s handleMessage(), regardless of who fetched the message, because whoever fetched the message is going to call handleMessage(). You can trace handleMessage() backwards. Since sendMessage() is the sendMessage() that implements Handler, take a look at who called the Handler’s sendMessage(). Once the search is done (it could have been done without searching, just one line below sendMessage), the code looks like this:

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

You can see that dispatchMessage() calls handleMessage(), so do a search to see where dispatchMessage() is called. Looper’s loop() calls dispatchMessage(). Loop () calls Looper’s loop() calls dispatchMessage(). Here’s the source code for loop() :

public static void loop(a) {
    // Omit part of the code
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // Omit part of the code

        try {
            msg.target.dispatchMessage(msg);
            // Omit part of the code
        } catch (Exception exception) {
            // Omit part of the code
            throw exception;
        } finally {
            // Omit part of the code
        }
        // Omit part of the codemsg.recycleUnchecked(); }}Copy the code

Loop () is an infinite loop that calls next() of Looper’s MessageQueue and assigns it to the Message object. The loop ends only when next() returns null. That’s when Looper’s thread ends. The fetched Message calls target.dispatchMessage(). We already know that target is the Handler that called sendMessage(). So this is where the Handler’s dispatchMessage() is called, and we’ve found the source of the handleMessage() callback. Based on the name and the assignment operation, we can assume that next is going to fetch the next message from the queue.

Message next(a) {
    for (;;) {
        // Omit part of the code
        nativePollOnce(ptr, nextPollTimeoutMillis);
        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) {
                // 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;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    returnmsg; }}else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null; }}}}Copy the code

This is also an indefinite loop with a nativePollOnce Native method, similar to Java’s object.wait (). NativePollOnce will block until a new Message arrives. And continues with the subsequent return Message code. The previous enqueueMessage() had a call code for nativeWake(), which is similar to Java’s object.notify (), to wake up the thread when a new Message is inserted into the Message queue. These two native layer methods don’t need to worry too much, except to know that next() is a blocking method that returns a new Message when there is a new Message, and that the thread blocks when there is no new Message, without wasting CPU cycles.

The last two lines of the source code above show that when the mquit bit is true next() returns null, the infinite loop in loop() breaks out and the thread terminates. If you search for mquit () in the MessageQueue class and assign mquit to true, follow quit() and find that Looper’s quit() and quitSafely() call this method with the following code:

public void quit(a) {
    mQueue.quit(false);
}
public void quitSafely(a) {
    mQueue.quit(true);
}
Copy the code

As you can see from the name and comment, both methods quit looper() with the difference that quit will quit immediately and quitSafely will finish processing the remaining messages in the current message queue before quitting. After calling quit and quitSafely, a second call to handler.sendMessage () will return false, that is, no more messages will be accepted and processed, and the Handler thread will terminate. So be sure to call handler.getlooper ().quitsafely () in time after the child thread runs out of Handler;

Why don’t we write looper.prepare () when we create Handler on the main thread

public static void main(String[] args) {
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Copy the code

PrepareMainLooper () is called in the main entry of the entire app, prepareMainLooper() is created, and loop() is called. Therefore, prepareMainLooper is created from the start of the main thread, and there is no need to manually call prepare(). PrepareMainLooper () ¶ Prepare (Boolean quitAllowed) is not allowed to exit from the main thread. So the Main thread Handler cannot call getLooper().quitsafely () and quit(), otherwise it will throw the Main thread not allowed to quit exception.

The process is clear: every time a thread new Handler() is created, that thread must have a Looper, which is stored in the thread’s ThreadLocal and retrieved globally throughout the thread. This Looper has a MessageQueue, and sendMessage() inserts the message into the MessageQueue. At the same time, Looper’s loop() loops to fetch messages from the Message queue. Every time a Message is fetched, the Handler’s handleMessage() is called. When the Message cannot be fetched, the thread blocks and waits. When null is removed, Looper’s quit() or quitSafely() is called, and the infinite loop in loop() pops out and the thread terminates. With endless loops and blocking mechanisms, you can respond quickly without wasting performance