Handler Message mechanism

Thread

The main thread = = > ActivityThread

Child Thread ==> New Thread

The Thread holding a ThreadLocal ThreadLocalMap

In ThreadLocal’s set/get method, thread.currentthread () gets the currentThread; Save its own ThreadLocal and Value to the ThreadLocalMap of the current thread.

Thus the binding of Thread and Value object is realized.

Public void set(T value) {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) return (T)e.value; } return setInitialValue(); }Copy the code

Looper

ActivityThread Main () calls looper.prepareMainLooper ()/ looper.loop (); No manual call required;

Looper.prepare()/ looper.loop () must be executed manually in the child thread. If looper.prepare () is not called, there is no Looper associated with Thread.

The Looper constructor holds MessageQueue and Thread, and a unique ThreadLocal;

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code

Looper is initialized with prepare;

/** 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)); }Copy the code

Sthreadlocal. set(new Looper(quitAllowed)) stores its own ThreadLocal and Looper to the ThreadLocalMap of the Thread.

This ensures that ThreadLocal and Looper are bound 1 to 1 and Thread and Looper are bound 1 to 1.

Thread==>ThreadLocal.ThreadLocalMap==>key:ThreadLocal; Value:Looper

Sthreadlocal. set(new Looper(quitAllowed));

Sthreadlocal.get ()! = null), which ensures that no data exists in a ThreadLocal until threadlocal. set is called.

Handler

The handleMessage() method is implemented by the caller to complete the logic;

public void handleMessage(@NonNull Message msg) {
}
Copy the code

HandleMessage () is called in the dispatchMessage() method;

Handler processes messages in the following order: Message’s Callback > Handler’s Callback > Handler’s handleMessage method

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

The new Handler first gets Looper. MyLooper () to hold a Looper;

public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    // ignore some 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

Stars. MyLooper () to obtain sThreadLocal. The get ()

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
Copy the code

Set (new Looper(quitAllowed)) in the looper.prepare () method. Associate a Thread with a Looper using a ThreadLocal.

If looper.prepare () is not called by new Handler in the child Thread, there is no Looper associated with the Thread.

The RuntimeException of the (mLooper == NULL) branch is executed

MessageQueue

MessageQueue. EnqueueMessage (), add a message to the message queue;

boolean enqueueMessage(Message msg, long when) { // ignore some code synchronized (this) { msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; 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

Messagequyue.next () fetches the message from the queue;

Message next() { // ignore some code for (;;) { 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(); return msg; } } 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

Sending and adding messages:

Handler.sendxxx()

Handler.postxxx()

Methods like //sendMessage/postxxx end up calling sendMessageAtTime

==>Handler.sendMessageAtTime()

QueueMessage () is executed in sendMessageAtTime().

==>MessageQueue.queueMessage();

QueueMessage () adds messages to the message queue;

MessageQueue stores messages in a priority queue;

The essence of MessageQueue: linked list + fifO queue + sorting by time ==> priority queue

The earlier the message is, the higher the priority is in the queue.

public boolean sendMessageAtTime(@NonNull 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

Getting messages and processing messages:

Looper.loop()==>MessageQueue.next()

Looper.loop() has a for loop that infinitely calls messagequeue.next ();

Messagequeue.next () returns the currently fetched message and executes after the message is fetched

==>Message.target.dispatchMessage(msg)

Message.gettarget () returns the Handler that sent the current Message;

HandleMessage is finally called in handler.dispatchMessage ()

==>Handler.handleMessage()

public static void loop() {
    // ignore some code
    final Looper me = myLooper();
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // ignore some code
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
    }
}
Copy the code

Message

SPool is used in message.obtain to share Message objects, rather than creating new messages each time;

If I say new Message every time; New memory is created each time, and the Message is destroyed after processing. Repeating this step will cause memory jitter

Therefore, use message.obtain () to create messages.

Handler target; public static final Object sPoolSync = new Object(); private static Message sPool; /** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }Copy the code

In the stars. The loop () and MessageQueue. RemovMessagexxx () call

/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */ @UnsupportedAppUsage void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; }}}Copy the code

Message.gettarget () returns the Handler that sent the current Message;

public Handler getTarget() {
    return target;
}
Copy the code

Simple flow chart

Thread==> Associates Looper==> with ThreadLocal to hold MessageQueue

Handler==> Associate and hold Looper==> Hold MessageQueue

Handler. SendMessage () = = > MessageQueue. QueueMessage () = = > message queue to add

Looper.loop()==> messagequyue.next ()==> handler.dispatchMessage ()==>Handler.handleMessage()==> Process the message

Looper.preparemainlooper ()/ looper.loop () is automatically called in main(); Looper.prepare()/ looper.loop () must be executed manually in the child thread.

Quit ()==> Messagequeue.quit ()==> Messagequeue.next () Returns null==> looper.loop () jumps out of the for loop and ends looper.loop ()==> The thread ends