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