Android advanced chapter 17, in-depth understanding of Handler source code analysis

Android advanced chapter 17, in-depth understanding of Handler source code analysis

Introduction:

Let's first take a macro look at the process of Handler sending and processing messages through sendMessage, as shown below, and will be analyzed in detail later:

handler.sendMessage -> messageQueue.enqueueMessage -> (looper.loop()) -> messageQueue.next() -> msg.target.dispatchMessage(msg) -> handler.handleMessage()

1. Handler source code analysis

1. Handler construction method

The construction method of Handler is divided into two categories according to whether we pass in the looper parameter;

1) When we call the public Handler (Looper looper) and public Handler (Looper looper, Callback callback) construction methods, the following construction methods will be called, which is to assign the looper and callback we passed in to the member variables in the Handler:

public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } Copy code

2) When we call the public Handler() or public Handler(Callback callback) construction method, the following construction method will be called. In Note 1, you can see that Looper is obtained through Looper.myLooper() and assigned to mLooper, Looper we It will be introduced below:

public Handler(Callback callback, boolean async) { ,, mLooper = Looper.myLooper();//1 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 code

2. Handler sends a message

When we send a message, the call chain in the handler is as follows:

sendMessage - sendMessageDelayed - sendMessageAtTime - enqueueMessage

The final enqueueMessage method called is as follows, there are three key operations:

In Note 1, set the target attribute of the message to this. What is the use of this target attribute? It will be used when we process the message to identify which handler handles this msg;

Note 2 will check if the member variable mAsynchronous of the Handler is true, set msg as an asynchronous message. We will talk about the knowledge of this asynchronous message when we synchronize the barrier later;

Note 3 is to call enqueueMessage of MessageQueue to put msg into the message queue;

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this;//1 msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true);//2 } return queue.enqueueMessage(msg, uptimeMillis);//3 } Copy code

3. Handler processes messages

When we analyze looper later, we will see that the message retrieved from the loop method will be distributed to the handler for processing, that is, the dispatchMessage method is called;

Note 1 indicates that if the callback field of the message is not empty, the handleCallback will be called, that is, the run method of the callback of the message will be called;

Note 2 means that if the mCallback field of the handler is not empty, the handleMessage method of mCallback will be called. We can pass in a custom Callback when creating the Handler;

Note 3 means that the first two conditions are empty, and we will call our own implementation of the handleMessage(msg) method in the handler;

public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg);//1 } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) {//2 return; } } handleMessage(msg);//3 } } Copy code

2. MessageQueue source code analysis

Before analyzing the source code of MessageQueue, we need to know the structure of Message. Message is similar to the structure of a linked list node. It maintains a next member variable inside, and the type is also Message, which is used to point to the next Message; and a linked list is maintained in MessageQueue. The implemented priority queue, the node of the linked list is Message;

1. enqueueMessage

Call sendMessage in the handler and finally call to queue.enqueueMessage, the method is as follows;

Note 1 means that there is no message in the queue or the waiting time of the inserted message is 0 or the waiting time is less than the waiting time of the message at the head of the queue. At this time, the newly inserted message is placed at the head of the queue;

Note 2 means that if these three conditions are not met, it will be inserted into the appropriate position in the queue; Note 3 means that if you need to wake up, call nativeWake;

boolean enqueueMessage(Message msg, long when) { ,, synchronized (this) { ,, msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when <p.when) {//1 //New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else {//2 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; } if (needWake) { nativeWake(mPtr);//3 } } return true; } Copy code

2. next

After analyzing enqueueMessage, let's analyze the next method in advance. Where is this method called? It is called in the loop method of looper to retrieve the message. The main source code is as follows;

The native method nativePollOnce in Note 1 is used for cpu sleep, which is also compatible with nativeWake, take the message to sleep, and the message enters the queue to wake up; and the message enqueue and dequeue are both in the synchronized (this) code block, and The locks are all this objects, so sending or fetching messages is mutually exclusive, and only one thread can operate at a time;

Note 2 is used to process asynchronous messages, we will analyze it later when we introduce the synchronization barrier; Note 3 is used to return the obtained message, and the message in the queue needs to be re-maintained before returning;

Message next() { ,, int nextPollTimeoutMillis = 0; for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis);//1 synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous());//2 } if (msg != null) { if (now <msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when-now, Integer.MAX_VALUE); } else { mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg;//3 } } else { nextPollTimeoutMillis = -1;//4 } } nextPollTimeoutMillis = 0;//5 } } Copy code

3. Looper source code analysis

If we do not pass in Looper when creating Handler, we will call Looper.myLooper() to obtain Looper object; when we use the handler message mechanism, we need to ensure that Looper has been created and the loop loop is turned on to make the message mechanism run normally, but we are When the handler is used in the main thread, these two operations are not performed, so where are these two operations performed?

The answer is to execute in the main method of ActivityThread. The main method of ActivityThread represents the execution entry of the main thread of our application process. The main method is shown below. Note 1 is to create the Looper object, and Note 2 is to open the loop loop;

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

1. Introduction to prepare method

Call prepare in the prepareMainLooper method;

Note 1 will be judged first to ensure that a thread can only have one Looper;

Note 2 is used to create a new Looper object and set it into ThreadLocal to ensure thread isolation; the quitAllowed parameter indicates whether the looper can exit. The prepare called in the main thread passes this parameter to fasle to indicate that it cannot be exited. When the looper is created in the thread, this parameter is defaulted to be true to indicate that it can be exited, and we must exit when we use it in the child thread, otherwise it will cause a memory leak;

private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) {//1 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));//2 } Copy code

The Looper created by the construction method is shown below. Note 1 indicates that the new MessageQueue object is created. Therefore, Looper and MessageQueue correspond to each thread, and each thread has only one;

private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed);//1 mThread = Thread.currentThread(); } Copy code

The set method of ThreadLocal is shown below. Here, the member variable threadLocalMap of each thread is obtained through getMap. ThreadLocal and Looper are saved in the Entry array in ThreadLocalMap, thus ensuring the isolation of threads; I will not introduce it in detail here, but introduce ThreadLocal in the previous section. Already introduced in the article;

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 code

2. Introduction to loop method

After introducing prepare, we have created Looper and MessageQueue and bound them to the corresponding threads; the loop method is introduced below to fetch messages from MessageQueue;

Note 1: When fetching messages from MessageQueue, two situations will cause blocking, one is that there is no message in the queue, and the second is that the earliest message in the queue has not yet reached the execution time;

Note 2 is used to distribute messages to the handler for execution;

Note 3 indicates that the message will be recycled after processing;

public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } ,, for (;;) { Message msg = queue.next();//might block//1 if (msg == null) { //No message indicates that the message queue is quitting. return; } ,, try { msg.target.dispatchMessage(msg);//2 } ,, msg.recycleUnchecked();//3 } } Copy code

3. Introduction to the quit method

When we create a looper in a child thread, we must call the quit method to exit the looper after using it, otherwise it will cause a memory leak; the quit method has only one line of code, that is, the quit method of MessageQueue is called;

public void quit() { mQueue.quit(false);//1 } Copy code

We then analyze the quit method of MessageQueue;

Note 1 indicates that the looper of the main thread cannot exit;

Note 2 set the mQuitting variable to true, and wake up the queue through note 3; when we set mQuitting to true, the next method of MessageQueue will return null, and then we judge in the loop method of looper if the returned msg is null, it will exit the for loop, and exit the loop;

void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit.");//1 } synchronized (this) { if (mQuitting) { return; } mQuitting = true;//2 nativeWake(mPtr);//3 } } Copy code

4. Analysis of other methods

1), getMainLooper is used to return the looper of the main thread;

public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } Copy code

2) If we create a looper in the child thread, myLooper is used to return the looper of the current thread;

public static @Nullable Looper myLooper() { return sThreadLocal.get(); } Copy code

3), myQueue is used to return the MessageQueue corresponding to the current thread;

public static @NonNull MessageQueue myQueue() { return myLooper().mQueue; } Copy code

4), getThread is used to return the thread corresponding to the current Looper;

public @NonNull Thread getThread() { return mThread; } Copy code

4. Message source code analysis

We have also analyzed before, Message is the node in the linked list, there is a next member variable inside, and the type is also Message, which is used to point to the next Message; when we create a Message, we usually use new Message or Message.obtain;

1. Analysis of obtain method

The obtain method is as follows, we maintain a Message pool in Message, sPool represents the first Message in the pool, if sPool is not empty, we reuse sPool, and put the first in the pool in Note 2 Update the element, and set the next of the reused Message to null in Note 3, and remove it from the linked list;

public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool;//1 sPool = m.next;//2 m.next = null;//3 m.flags = 0;//clear in-use flag sPoolSize--; return m; } } return new Message(); } Copy code

2. Analysis of recycleUnchecked method

We take out the message in the loop of the looper and distribute it to the handler for processing. When the message is processed, msg.recycleUnchecked() will be called, which is the message recovery performed;

As shown below, first clear the fields of Message, and then point the next of the recycled message to sPool in Note 1;

Reset sPool to the recycled message in Note 2 and maintain the number of elements in the pool in Note 3;

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;//1 sPool = this;//2 sPoolSize++;//3 } } } Copy code

5. synchronization barrier

Synchronous barrier, as the name implies, is to shield synchronous messages and process asynchronous messages;

In the next method of MessageQueue, there is the following piece of code. In Note 1, if it is judged that msg is not empty, and then msg.target is empty, the synchronization barrier will be triggered.

Look for asynchronous messages in the while loop in Note 2; we can call setAsynchronous(true) when creating a message to set the message as an asynchronous message; then how does the msg.target == null condition meet?

if (msg != null && msg.target == null) {//1 //Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous());//2 } Copy code

The synchronization barrier is opened by calling the postSyncBarrier method. In note 1, we found that the created msg did not assign a value to the target field, so it was empty, so the synchronization barrier conditions were met; then this msg will be inserted into the queue below in;

private int postSyncBarrier(long when) { //Enqueue a new sync barrier token. //We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token;//1 Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) {//invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } Copy code

The synchronization barrier is used to handle some urgent tasks. We open the synchronization barrier and then set the message as an asynchronous message. After we have processed the asynchronous message, we need to close the synchronization barrier. Otherwise, the synchronous message cannot be retrieved when the next message is retrieved, so how to cancel What about synchronization barriers?

Cancel the synchronization barrier by calling removeSyncBarrier, the method is as follows, we are canceling the synchronization barrier at Note 1 or Note 2, this p message is the msg that opened the synchronization barrier, we pass prev.next = p.next or mMessages = p.next removes this message from the queue.

public void removeSyncBarrier(int token) { //Remove a sync barrier token from the queue. //If the queue is no longer stalled by a barrier then wake it. synchronized (this) { Message prev = null; Message p = mMessages; while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } if (p == null) { throw new IllegalStateException("The specified message queue synchronization" + "barrier token has not been posted or has already been removed."); } final boolean needWake; if (prev != null) { prev.next = p.next;//1 needWake = false; } else { mMessages = p.next;//2 needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); //If the loop is quitting then it is already awake. //We can assume mPtr != 0 when mQuitting is false. if (needWake && !mQuitting) { nativeWake(mPtr); } } } Copy code

6. the typical application of the synchronization barrier in the system

When the View is updated, many places such as draw, requestLayout, invalidate, etc. call ViewRootImpl#scheduleTraversals(). Let s take a look at the implementation of this method. Note 1 is to open the synchronization barrier; Note 2 s postCallback method is used to send asynchronous messages

void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//1 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//2 notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } Copy code

The postCallback method will eventually call postCallbackDelayedInternal, as shown below. Note 1 indicates that this is an asynchronous message;

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { if (DEBUG_FRAMES) { Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token + ", delayMillis=" + delayMillis); } synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true);//1 mHandler.sendMessageAtTime(msg, dueTime); } } } Copy code

We need to cancel the synchronization barrier after processing the asynchronous message, so when is it canceled? The answer is the ViewRootImpl#unscheduleTraversals() method, as shown below, in Note 1 the method to cancel the synchronization barrier is called;

void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//1 mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } Copy code

7. the sub-thread creates the IntentService of the typical application of Looper

We know that IntentService, as a special Service, can automatically execute the onHandleIntent method we rewritten in the child thread, and automatically stop the service after execution. Let s analyze how to implement it below;

ServiceHandler is an internal class of IntentService, which implements unique logic in the handleMessage method, so when will this handleMessage be called?

private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } Copy code

The onCreate and onStart methods of IntentService are as follows. HandlerThread is created in Note 1, and then the looper corresponding to this thread is obtained in Note 2; In Note 3, a handler is created by the looper created;

Note 4 that sending a message through this handler triggers our handleMessage method above, so what is this HandlerThread? We will introduce it in the next section.

@Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");//1 thread.start(); mServiceLooper = thread.getLooper();//2 mServiceHandler = new ServiceHandler(mServiceLooper);//3 } @Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg);//4 } Copy code

8. HandlerThread, a typical application of Looper created by sub-threads

The run method of HandlerThread is shown below. When we call the start method of this thread in IntentService, this run method will be called. You can see that note 1 and note 3 call prepare and loop respectively. This is also the key to creating looper in the child thread. ; The lock at Note 2 is used to cooperate with the getLooper method;

public void run() { mTid = Process.myTid(); Looper.prepare();//1 synchronized (this) {//2 mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop();//3 mTid = -1; } Copy code

The getLooper method is shown below. The lock at Note 1 is matched with the run method. Note 2 means that when the mLooper is still empty, you need to call wait to release the lock and wait, and then call notifyAll to wake up wait after assigning a value to mLooper in the run method;

public Looper getLooper() { if (!isAlive()) { return null; } //If the thread has been started, wait until the looper has been created. synchronized (this) {//1 while (isAlive() && mLooper == null) { try { wait();//2 } catch (InterruptedException e) { } } } return mLooper; } Copy code