Abstract

Android applications are message-driven, and a message queue is created internally when the Android main thread starts. It then enters an infinite loop, polling for new messages to be processed. If there is a new message, process it. If there is no message, it is blocked until the message loop is woken up.

So how does message processing work in Android? During program development, we often use handlers to handle messages. Handler is the Message Handler and Message is the Message body. In addition, there are two roles: message queuing and message polling. They are MessageQueue, which is the MessageQueue, and Looper, which polls messages.

Introduction to the

We already know that The Android messaging mechanism is mainly handled by the implementation of Handler, Message, MessageQueue and Looper classes. So what’s the relationship between them?

Among them, Message is the Message body, which is responsible for storing various information of the Message, including the Handler object that sends the Message, Message information, Message identifier, etc. A MessageQueue is a Message queue that maintains a set of messages as queues inside it. Handler is responsible for sending and processing messages. Looper is responsible for polling message queues.

Principle of Android message mechanism

Create a thread message queue

In Android applications, the MessageQueue (MessageQueue) is created before the message handler runs. In the main thread, the message queue is created by calling the static member function prepareMainLooper() of the Looper class. In other child threads, it is created by calling the static member function prepare().

PrepareMainLooper () and prepare()

/** * 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: */ public static void prepareMainLooper() {prepare(false); public static void prepareMainLooper();  synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } public static @Nullable Looper myLooper() { return sThreadLocal.get(); } /** 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); */ 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)); }Copy the code

The sThreadLocal variable is used during both function calls. This variable is of type ThreadLocal and holds the Looper object in the current thread. This means that for every message queue created in an Android application, there is one and only Looper object corresponding to it. And we can see from the source code that exceptions are thrown when objects are not unique.

private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); MThread = thread.currentThread (); }Copy the code

As you can see from the source code above, a message queue is created when the Looper object is instantiated.

Message loop process

After the message queue is established, the message loop begins by calling the static member method loop() of the Looper object.

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ 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; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) {// start the loop Message MSG = queue.next(); If (MSG == null) {if (MSG == null) {return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging ! = null) {... } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag ! = 0 && Trace.isTagEnabled(traceTag)) {... } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); End = (slowDispatchThresholdMs == 0)? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag ! = 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) {... } } if (logging ! = null) {... } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent  = Binder.clearCallingIdentity(); if (ident ! = newIdent) {... } msg.recycleUnchecked(); }}Copy the code

The above source code is the process of the message loop. The message loop only works when the loop() method is called. When the loop starts:

  • Gets the current thread’s Looper object, if null, throws an exception;

  • Get the message queue and start the message loop;

  • Retrieves the message from the MessageQueue (calling MessageQueue’s next() method), and if null, terminates the loop; Otherwise, continue.

  • Process messages, recycles message resources (msg.recycleunchecked ()).

During the message loop, messages are served through MessageQueue’s next() method, sleep when there is no information, and work with the other interfaces. This process is critical, and the next() method also determines whether the message loop exits.

Message next() { final long ptr = mPtr; If (PTR == 0) {return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; //0 does not go to sleep,-1 goes to write for (;;) { if (nextPollTimeoutMillis ! = 0) {/ / deal with the current thread to be processed in the Binder interprocess communication request Binder. The flushPendingCommands (); 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; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; NextPollTimeoutMillis = -1;}} else {// No more messages. NextPollTimeoutMillis = -1; // Process the quit message now that all pending messages have been handled. dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. // The IdleHandler interface is processed when not sleeping 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); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; }}Copy the code

Message loop exit process

As you can see from above, the loop() method is an infinite loop that only ends when MessageQueue’s next() method returns NULL. When is the next() method of MessageQueue null?

In the Looper class we see two terminating methods quit() and quitSalely(). The difference between the two is that the quit() method ends the loop directly, disposing of all the messages in the MessageQueue, whereas the quitSafely() method quitSafely() does not quit until the remaining non-delayed messages in the MessageQueue are processed (delayed messages are recycled). Both methods call MessageQueue’s quit() method.

void quit(boolean safe) { if (! mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; / / set the exit status / / processing messages in the queue if (safe) {removeAllFutureMessagesLocked (); } else {removeAllMessagesLocked(); // We can assume mPtr! = 0 because mQuitting was previously false. nativeWake(mPtr); // Wake up message loop}}Copy the code

Process messages in message queues: Choose different processing options based on the Safe flag.

Private void removeAllMessagesLocked() {Message p = mMessages; while (p ! = null) { Message n = p.next; p.recycleUnchecked(); P = n; } mMessages = null; 18 * * * *} / API Level to get rid of all the delay message in the message queue * / private void removeAllFutureMessagesLocked () {final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p ! = null) { if (p.when > now) { removeAllMessagesLocked(); } else { Message n; for (;;) { n = p.next; if (n == null) { return; } if (n.hen > now) {// find the delay message break; } p = n; } p.next = null; // All messages after the first delayed message are delayed messages since messages are sorted by execution time in the message queue do {p = n; n = p.next; p.recycleUnchecked(); } while (n! = null); }}}Copy the code

Message sending process

In Android applications, messages are sent to a thread’s message queue through the Handler class. Each Handler object holds a Looper object and a MessageQueue object.

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

        mLooper = Looper.myLooper(); //获取Looper对象
        if (mLooper == null) {...}
        mQueue = mLooper.mQueue;  //获取消息队列
        mCallback = callback;
        mAsynchronous = async;
    }
Copy the code

In the Handler class, we see multiple sendMessage methods that all end up calling the same method, sendMessageAtTime().

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); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }Copy the code

As you can see, the two methods are easy to understand by calling the enqueueMessage() method on the MessageQueue object to add messages to the MessageQueue.

boolean enqueueMessage(Message msg, Long when) {// Handler is null 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) {// Quit 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; boolean needWake; If (p = = null | | the when = = 0 | | the when < p.w hen) {/ / New head, wake up the event queue if blocked. / / queue no news, Add 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; / / according to the execution time is inserted into the corresponding location if (p = = null | | the when < p.w hen) {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); // Wake up message loop}} return true; }Copy the code

From the source code, the following steps are required for a message to be inserted into a message queue:

  • The message holds a null Handler object, which throws an exception; Throw an exception when the message has been consumed;

  • When there is no message in the message queue, it is inserted directly.

  • When there is a message in the message queue, the message is inserted to the corresponding position by comparing the execution time of the message.

  • Determine if you need to wake up the message loop.

Message processing

During the message loop, if a new message is added, the message is processed. From the above analysis, we can see that in the message loop, the target message calls the dispatchMessage() method of its Handler object, which is the method used to process the message.

/** * Handle system messages here. */ public void dispatchMessage(Message MSG) {// Callback interface is not null (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! If (McAllback.handlemessage (MSG)) {return; if (McAllback.handlemessage (MSG)) {return; } } handleMessage(msg); // Process messages}}Copy the code

As can be seen from the source code, the Handler handles messages in three cases.

  1. When the callback in Message is not null, the method in the callback in Message is executed. This callback is a Runnable interface.

  2. When the Callback interface in this Handler is not null, the method in the Callback interface is executed.

  3. Execute the handleMessage() method in the Handler directly.

Why doesn’t the main thread freeze when Looper starts calling loop()

Looper.loop() goes into an infinite loop, so if the loop is executed on the main thread, it doesn’t cause the main thread to freeze, or other operations on the main thread, such as UI operations, can proceed smoothly. Because Android applications are message-driven, Android applications operate through Android’s messaging mechanism. At this point, you need to analyze the Entry class ActivityThread for Android application launches. We all know that when an Android application launches, the Java layer starts with the Main () method of the ActivityThread, which is what we call the main thread.

public static void main(String[] args) { ... . . Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); // Bind with ActivityManagerService if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler(); }... . . Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code

From the Main () method of the ActivityThread we can see Looper initialization and the start of the message loop, as well as the key attach() method linking to the ActivityManagerService. Links are created for subsequent events in the corresponding Activity. Initialization of the Native layer Looper is also involved. During Looper initialization, a pipe is set up to maintain read and write of the message queue and listen for read and write events through the epoll mechanism (an IO multiplexing mechanism).

  • When there are no new messages to be processed, the main thread blocks on the pipe until there are new messages to be processed;

  • When other threads have messages to send to the message queue, they write data through the pipe.

When we debug the program, we can see this by looking at the function call stack:

This confirms the initial statement that Android applications are driven by messages.

Whether all messages are executed at the specified time

PostDelay (action, 1000) : postDelay(action, 1000); postDelay(action, 1000) : postDelay(action, 1000);

The answer is not necessarily. We only store Android messages in the message queue in chronological order. If we add multiple messages to the queue, such as 10,000 messages that are executed with a delay of 1000ms, the execution time of the last message and the first message will be different.

conclusion

At this point, the message processing mechanism of the Android system is analyzed. Message processing in Android applications is divided into three main processes:

  1. Start the message loop in Looper and start listening on the message queue.

  2. Sends a message to a message queue via Handler.

  3. The Handler object is called through the message loop to process the newly added message.

When using message queues, message queues are created in the main thread when the program starts, so we do not need to initialize message loops and message queues when using the message mechanism in the main thread. We need to initialize the message queue in the child thread, and note that Looper’s quit or quitSafely method should be called to close the message loop when the message queue is no longer needed. Otherwise, the child thread may stay in a waiting state.