preface

The daily development of Android is inseparable from the message mechanism. This topic mainly studies and discusses the message mechanism of Android.

Android message mechanism is mainly implemented through Handler, and the Handler layer needs MessageQueue and Looper to implement MessageQueue and message processing respectively.

1. An overview of the

Let’s first look at what handlers do.

MessageQueue is a MessageQueue used to store messages, Looper is a Looper used to process messages, so what does Handler do? The main function of a Handler is to put a certain task (such as sending messages, processing messages) into a specific thread for execution.

  1. Why does it have to be executed in a particular thread?

    This is because Android states that UI operations can only be performed on the main thread, and there is a UI action thread validation in ViewRootImpl. The details are as follows:

    //ViewRootImpl.java
        void checkThread(a) {
            if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
  2. Why should UI operations only be performed on the main thread?

    First of all, look at the SOURCE UI controls know that the control is thread unsafe, so multi-threaded parallel operation of the UI will lead to the UI control state is not controllable.

  3. Why not make it thread-safe?

    Thread-safe means locking, which means that UI access logic is cumbersome and makes UI access less efficient, since locking can block other threads.

    Therefore, it seems that the UI can only be operated from the main thread, but Android says that time-consuming operations should not be used in the main thread, otherwise it may cause ANR (Application No Response).

So what do we do when we want to do a time-consuming process like a UI change and we have to do it on the main thread?

That’s where the Handler mechanism comes in, which switches UI operations to the main thread.

The process is as follows:

The Handler uses the POST method to put the Runnable into Looper for processing, or the send method to send the Message to Looper for processing.

Taking the postDelayed method as an example,

//Handler.java

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {...return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; .return enqueueMessage(queue, msg, uptimeMillis);
    }

   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {...return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

The internal invocation process is roughly as follows, with the POST method essentially calling the Send method and then callingMessageQueueIn theenqueueMessageTo add the message to the message queue.

//MessageQueue.java

    boolean enqueueMessage(Message msg, long when) {...synchronized (this) {//Handler internally locks itself
            if (mQuitting) {// Check whether the thread has been destroyed. }// Occupy the message and note how long it is occupied
            msg.markInUse();
            msg.when = when;
            // Start adding messages to the message queue
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                // The new message queue header, if blocked, wakes up the event queue
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else{... msg.next = p;// invariant: p == prev.nextprev.next = msg; }... }return true;
    }
Copy the code

This section provides a general idea of the process and does not analyze the code.

After that, Looper detects that a new message has arrived and processes it.

MessageQueue can be used to store messages, but the messages are not directly added to the MessageQueue. Instead, they are delivered to Looper via Handler objects.

//MessageQueue.java

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

Copy the code

In the Looper source code, the final processing of the message is given in the comments. Threads and message queues are also declared internally.

To understand the relationship among the three, it can be said that Looper is the commander, Handler is the executive officer, MessageQueue is the barracks and Message is the soldier. The Looper is responsible for arranging troops and issuing various command operations (Message processing) to the minions. The command operations are conveyed by the Handler to the barracks (MessageQueue) and executed for the minions (handleMessage).

//Looper.java
/ * *...... * 
 * class LooperThread extends Thread { * public Handler mHandler; * * public void run() { * Looper.prepare(); * * mHandler = new Handler() { * public void handleMessage(Message msg) { * // process incoming messages here * } * }; * * Looper.loop(); * } * }

*/
public final class Looper {

private static final String TAG = "Looper";

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class

final MessageQueue mQueue;
finalThread mThread; . }Copy the code

It is also important to note that Looper runs in the thread that created the Handler, so UI operations in the Handler will be switched to the thread that created the Handler.

2. ThreadLocal

Before we formally introduce the Handler mechanism, we introduce the concept of ThreadLocal, which has a significant impact on the Handler mechanism.

The observant may have noticed the definition of ThreadLocal in Looper.

//Looper.java

public final class Looper {...static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>(); . }Copy the code

ThreadLocal is a datastore class that stores only the data of the thread itself. Analogous to the scope of a variable in code, data inside the thread (variables) cannot be obtained outside the thread (scope).

2.1 Application Scenarios

  1. The current data is only available in this thread, and different threads have different copies

    For example, a Looper is a Handler. Each thread needs to have its own Looper to handle its own message. The scope of a Looper is obviously that of its own thread, so ThreadLocal is a good choice.

  2. Object passing in complex logic business

    For example, the passing of listeners, some tasks are too complex, require a relatively deep function call stack, and expect listeners to run through the thread at the same time. In general, there are two solutions: one is to pass the listener along as an argument, or the other is to define the listener as a static variable. There are problems with both of these approaches. The first approach, passing the listener as an argument all the time, sounds very problematic. It’s a very cumbersome and unreasonable program design. Second, the scalability is relatively poor, do not know how many threads are executing in advance may not be able to determine the number of listeners, only write dead flexibility.

    ThreadLocal does not have this problem. Each thread stores its own listeners, and if it wants to get them, it can write a get method to get them.

2.2 Small Practice

Declare a Boolean ThreadLocal variable.

As follows, in the main thread, thread 1 and thread 2, the log is printed separately, showing the get value.

The results are as follows:

2.3 ThreadLocal source code parsing

2.3.1 set method

The set method in ThreadLocal, ThreadLocalMap, is an inner class of ThreadLocal. It is essentially a Map with a key of ThreadLocal and a value of the generic type T in ThreadLocal. Use a key to store the array table, so that something like hash table development address method resolve hash conflicts.

The construction of ThreadLocalMap is already marked in the code. The logic of this section is relatively simple. It is equivalent to writing an open address method to handle hash conflicts and custom Entry to receive key-value pairs.

Instantiating a ThreadLocal object and using its set method first determines whether the current thread has its own ThreadLocalMap, and if it does, sets the current thread a key-value pair . (the>

This process essentially means that a ThreadLocal saves itself to the corresponding Thread’s ThreadLocalMap.

//ThreadLocal.java

   static class ThreadLocalMap {...// Key/value pair, weak reference? I don't really understand this
        static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}// The initial capacity must be a power of 2
        private static final int INITIAL_CAPACITY = 16;

        // The key-value array must be a power of 2
        private Entry[] table;

        // Number of key-value pairs
        private int size = 0;

        // Expand the threshold
        private int threshold; // Default to 0

       
       // The loading factor is a multiple of 2/3 of the length
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /** * Increment i modulo len. */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /** * Decrement i modulo len. */
        private static int prevIndex(int i, int len) {
            return ((i - 1> =0)? i -1 : len - 1);
        }

       // Constructor to initialize a ThreadLocalMapThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];// Array length
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// Calculate the hash value and place it in the appropriate position
            table[i] = new Entry(firstKey, firstValue);
            size = 1; setThreshold(INITIAL_CAPACITY); }... }ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
    }

Copy the code

The Thread class has a variable of type ThreadLocalMap that holds the ThreadLocal data of the Thread.

//Thread.java

	/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

Copy the code

The get method 2.3.2

//ThreadLocal.java

	public T get(a) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if(map ! =null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if(e ! =null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    returnresult; }}return setInitialValue();
        }

        private T setInitialValue(a) {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if(map ! =null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
Copy the code

3. The MessageQueue

A Message queue (MessageQueue) is used to store messages. The main operation is to insert (enqueueMessage()) messages into the queue and to read next() messages out of the queue. Interestingly, the MessageQueue becomes a queue but is essentially a linked list.

//MessageQueue.java
public final class MessageQueue {... Message mMessages; . }//Message.java
public final class Message implements Parcelable {...// sometimes we store linked lists of these things
    /*package*/Message next; . }Copy the code

The operation of the message joining the queue has been discussed in the overview of the message distribution mechanism. The following is the message reading process.

   Message next(a) {...int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {// Loop indefinitely until a message is returned
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            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());// Loop to find the message
                }
                if(msg ! =null) {
                    if (now < msg.when) {
                        // The new message is not ready to be queued, set the time to wake up, also blocking
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;// Get the message
                        // Delete the message from the list
                        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();// Schedule processed messages
                    return null; }...if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;// Block if there is no incoming message
                    continue; }... }... }}Copy the code

You can see that when no new messages are received, the next method keeps going through the loop, blocking (mBlock); If a new message is received, it is returned and removed from the list.

4. Which role

Looper is mainly used to loop detect messages in the message queue, that is, loop is the main function of the Looper module. As can be seen from the code, the Looper will break out of the loop only when there is no message in the message queue.

//Looper.java
public final class Looper {
    
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
    
    // The constructor
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);// Message queue
        mThread = Thread.currentThread();// Thread where Looper is located
    }
    
    public static @Nullable Looper myLooper(a) {
        return sThreadLocal.get();// Get the thread Looper
    }
    // Loop the main function
    public static void loop(a) {
        final Looper me = myLooper();// Get this thread Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        finalMessageQueue queue = me.mQueue; .//Looper loops to check the message queue
        for (;;) {
            Message msg = queue.next(); // The next method extracts the message from the message queue
            if (msg == null) {
                // Exit only if the message is empty
                return; }...final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);// Message scheduling processing
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if(traceTag ! =0) { Trace.traceEnd(traceTag); }}... msg.recycleUnchecked();// If there is a message, mark it with inUse}}... }Copy the code

If Looper receives a message, it will process the message by calling MSG. Target, which is a Handler object, calls the Handler’s dispatchMessage method to process the message.

//Message.java
   public final class Message implements Parcelable {.../*package*/Handler target; . }Copy the code

Before Looper can work, some preparations need to be made, that is, call prepare() to create a Looper in advance, and then call loop() to open the loop. But a lot of times we seem to implement the Message mechanism by defining the Handler directly on the main thread and defining the Message to send. This is because the ActivityThread, the main thread, already creates a Looper, but unlike other methods, PrepareMainLooper provides the prepareMainLooper method to define the Looper of the main thread for this particular thread (that is, the main thread), and there are sMainLooper and getMainLooper for obtaining the Looper of the main thread.

//Looper.java  
	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));
    }

    public static void prepareMainLooper(a) {
        prepare(false);
        synchronized (Looper.class) {
            if(sMainLooper ! =null) {
                throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}public static Looper getMainLooper(a) {
        synchronized (Looper.class) {
            returnsMainLooper; }}Copy the code

Looper can quit, of course. Looper has the quit and quitSafely methods, but it calls the exit method of the message queue and the message queue clears itself. The MSG Looper receives in the loop is = queue.next() => MSG = null, Also exit the loop and end.

//Looper.java

   public void quit(a) {
        mQueue.quit(false);
    }

    public void quitSafely(a) {
        mQueue.quit(true);
    }


//MessageQueue.java

    void quit(boolean safe) {
        if(! mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr ! = 0 because mQuitting was previously false.nativeWake(mPtr); }}Copy the code

5. A Handler function

After the above process, we can make the process relatively concrete (listing the key functions) as follows:

The Handler uses the post/send method to call enqueueMessage to add the message to the MessageQueue, and then Looper loops to detect the new message and then calls the Handler’s dispatchMessage to process the message.

We’ve already looked at the post and send methods of the Handler, so now we only have the dispatchMessage method. The process is as follows:

  1. If the message callback interface is not empty, the message callback(Runnable) is called directly, which is the Runnable parameter of the Handler post.

  2. If it is null, it determines whether its own interface mCallback is null. If it is not, it calls the interface’s message processing mechanism. If the processing fails, there is finally a subclass overwritten handleMessage that can be called.

  3. Note that when we define a Handler object, we override the handleMessage method, which skips the second step, since we don’t pass or set a CallBack interface. Therefore, it is possible to set a CallBack using a different constructor to the Handler, and handle the message in the handleMessage of the CallBack interface.

//Message.java
    /*package*/ Runnable callback;

//Handler.java
	//m.callback is the Runnable object in the POST method
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public interface Callback {
        
        public boolean handleMessage(Message msg);
    }
    
    public void handleMessage(Message msg) {}public void dispatchMessage(Message msg) {
        if(msg.callback ! =null) {/ / not be empty
            handleCallback(msg);// Then call handleCallback
        } else {
            if(mCallback ! =null) {
                if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}private static void handleCallback(Message message) {
        message.callback.run();
    }

Copy the code

6. The process diagram of the Android messaging mechanism

conclusion

This paper mainly introduces the message mechanism of Android and analyzes how Handler, MessageQueue and Looper work in coordination from the source level.