This article in the form of source code analysis + practical application, detailed explanation of the principle of the Handler mechanism, as well as in the development of the use of the scene and to pay attention to the place.
A review of basic principles
In Android development, Handler and related derivative applications are often used, and Android is built on this mechanism, so it is very necessary for every developer to understand the details of the principle and the pit. The Handler mechanism consists of Handler, Thread (ThreadLocal), Looper, MessageQueue, and Message.
1, Thread (ThreadLocal)
A Handler must be bound to a Looper that corresponds to a Thread. Loopers are created and stored one-to-one with threads. This means that each Thread can create a unique and unrelated Looper. This is done with ThreadLocal, which stores Looper objects for thread isolation.
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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
2, the Handler
Handler()
Handler(Callback callback)
Handler(Looper looper)
Handler(Looper looper, Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper, Callback callback, boolean async)
Copy the code
2.1 There are two general methods for creating a Handler:
One is not to pass Looper
Create a default Looper for the current thread by calling looper. prepare before creating the Handler, otherwise an error will be reported.
One is to pass in the specified Looper
The Handler is bound to the specified Looper, which means that the Handler can be bound to any thread, not just the thread in which it was created.
2.2 async parameter
Handler has an async parameter that indicates that all messages sent through this Handler are asynchronous messages, because this flag is set to message when the message is queued. This flag is global, which means that by constructing the async parameter passed to the Handler function, we determine that all messages sent through this Handler are asynchronous messages. The default value is false, which means that all messages sent through this Handler are synchronous messages. As for the specific purpose of this asynchronous message, we will talk about barrier messages later.
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
2.3 the callback parameter
This callback parameter is a callback after the message has been dispatched, and eventually when MSG calls Handler’s dispatchMessage, based on the actual situation:
public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code
3, which
A class for running a message loop for a thread. Default threads have no Looper associated with them; So call prepare() on the thread running the loop, and loop() to loop through the message until the loop stops.
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 loop() { ... for (;;) {... }... } class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { Message msg=Message.obtain(); }}; Looper.loop(); }}Copy the code
Since prepare must be called to create Looper before Looper can be used, why don’t we normally see prepare called in the main thread? This is because Looper is created by default in the ActivityThread entry main method when the Android main thread is created.
public static void main(String[] args) { ... Looper.prepareMainLooper(); . Looper.loop(); . }Copy the code
Let’s review the relationship between Looper related classes:
4. MessageQueue and Message
MessageQueue is a Message queue. The Handler sends a Message to the Message queue, and the Message queue retrieves the Message to be executed according to certain rules. Messages are not added directly to MessageQueue, but are associated with Looper via Handler objects.
The message in MessageQueue is sorted by time. The earlier the message joining the queue is placed at the head of the queue and the priority is executed. This time is transmitted when sendMessage is sent. The default is to use the non-sleeping time systemclock. uptimeMillis() for the current system from startup to now.
SendMessageAtFrontOfQueue incoming time this method is 0, meaning that calls this method of message will head into the message queue, but not easy to use, this method is easy to cause problems.
There are three types of messages stored in MessageQueue: synchronous messages, asynchronous messages, and barrier messages.
4.1 Synchronizing Messages
The async parameter of the Handler constructor is false by default. The async parameter of the Handler constructor is false by default. The async parameter of the Handler constructor is false by default.
4.2 Asynchronous Messages
An asynchronous Message creates a Handler if the incoming async is true or the incoming Message is sent via msg.setasynchronous (true); The latter message is an asynchronous message, and the asynchronous message function is only effective with the barrier message described below, otherwise it is treated as synchronous message.
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; If (mAsynchronous) {msg.setasynchronous (true); if (mAsynchronous) {msg.setasynchronous (true); } return queue.enqueueMessage(msg, uptimeMillis); }Copy the code
4.3 Barrier Messages
A Barrier is a special type of Message whose target is null. A Barrier is a Message whose target is null. And the arg1 attribute is used as a barrier identifier to distinguish between different barriers. Barriers are used to block synchronous messages and allow asynchronous messages in queues.
So how are barrier messages added and removed? We can see that there are methods to add and remove barrier messages in MessageQueue:
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; Message prev = null; Message p = mMessages; if (when ! If p points to a message with a smaller timestamp than the barrier message, the message is queued before the barrier message, and the barrier message is not affected by the barrier message. Find the first message pointer that entered after the barrier message. = null && p.when <= when) { prev = p; p = p.next; }} // The barrier message is inserted into the message queue, i.e. the barrier message points to the first message p, and the previous prev points to the barrier message, which completes the insertion. if (prev ! = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else {// If prev is null, the barrier message is at the head of the message queue. msg.next = p; mMessages = msg; } return token; } } 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; // A token is generated after the barrier message is inserted. This token is used to delete the barrier message. P while (p!) p 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!) {// if (prev! = null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; 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 the code
4.4 Role of barrier messages
Having said that, where do barrier messages work? What does it have to do with asynchronous messaging mentioned earlier? We can see that MessageQueue’s next method has this paragraph:
MSG. Target ==null, if there is a barrier message, then only the asynchronous message is allowed to continue execution, synchronous message block, until the barrier message is removed. if (msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; Async = async; async = async; async = async; async = async; async = async; } while (msg ! = null && ! msg.isAsynchronous()); }Copy the code
4.5 Practical application of barrier messages
The purpose of a barrier message is to block synchronous messages that are queued after it, but asynchronous messages are pulled out and executed in the normal order. What is its practical use? We see ViewRootImpl. ScheduleTraversals () used to barrier messaging and asynchronous messaging.
TraversalRunnable run(), in which doTraversal() is performed, which ultimately triggers the View’s rendering process: measure(),layout(),draw(). To allow the drawing process to be executed as quickly as possible, synchronous barriers are used.
void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; MTraversalBarrier = mhandler.getLooper ().getQueue().postsyncBarrier (); MTraversalRunnable, which will execute doTraversal(), which will trigger the View's rendering process The asynchronous message View drawing is executed first for interface drawing. // This is easy to understand, the task of drawing the interface must take priority, otherwise the interface will be stuck. mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } 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; // Set the message to be asynchronous MSG. SetAsynchronous (true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code
4.6 What can we do with barrier messages?
So aside from using barrier messages in the system, are there any scenarios we can use in development? The use of barrier messages to block synchronous messages can be used to implement UI initialization and data loading simultaneously.
When creating an Activity, in order to reduce the occurrence of null pointer exceptions, we usually setContent in onCreate first, then initialize the controller with findView, and then perform the asynchronous request for network data loading. After the network data loading is complete, we refresh the interface of each control.
Imagine using the barrier messaging feature to enable interface initialization and asynchronous network data loading simultaneously without affecting interface rendering. Let’s start with a sequence diagram:
We can further understand this by using the following pseudocode:
// Load the Data on the next page asynchronously from the previous page. // Creating a barrier message generates a token, which is used to delete the barrier message. int barrierToken; HandlerThread thread = new HandlerThread("preLoad"){@override protected void onLooperPrepared() { Handler mThreadHandler = new Handler(thread.getLooper()); Mhandler. post(new Runnable() {@override public void run() { Network request data, assign to netWorkData netWorkData = XXX; }}); BarrierToken = thread.getLooper().getQueue().postsyncBarrier (); // This message will not be executed until the barrier message is removed. Mhandler. post(new Runnable() {@override public void run() {// netWorkData assigned to the listener, refresh interface}}); }}; thread.start(); Protected void onCreate(Bundle savedInstanceState) {setContentView(view); Button BTN = findViewById(r.i.xxx); . // 4, to complete the control initialization, the asynchronous thread set the barrier message remove, so that the asynchronous thread request data is completed, 3, at the refresh UI synchronization message has the opportunity to execute, you can safely refresh the interface. thread.getLooper().getQueue().removeSyncBarrier(barrierToken); }Copy the code
However, MessageQueue source code we can see that the barrier message creation and deletion are hidden methods (@hide), we can not call directly, can only use reflection to call, so in the actual use of comprehensive verification.
4.7 IdleHandler and Applications
IdleHandler, which literally means the IdleHandler (that is, I execute when the message queue is idle, if there are other non-idlehandler messages in the message queue, I don’t execute), it’s actually an interface, so let’s just say it’s idle, Except instead of storing it in MessageQueue, it’s stored as an array.
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
Copy the code
MessageQueue has methods to add and remove idleHandlers, which are stored in an ArrayList:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); . public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } } public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); }}Copy the code
So how does it get executed in the gap between the message queues being idle? Again, look at the next() method.
// 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. PendingIdleHandlerCount < 0 = = null | | now < mMessages. When) is that the current message queue no later than the current message or to perform message queue now in free time / /. 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; }Copy the code
After the above code determines that the current message queue is free, it will get the size of the free message. The following code executes the free message.
// Run the idle handlers. // We only ever reach this code block during the first iteration. 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 {// If queueIdle returns true, the message will not be deleted automatically and will be executed again the next time the queue is still idle. // If false is returned, the idle message will be deleted automatically after execution. 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
To sum up:
- If the loop receives an empty message, or if the message is delayed and has not reached the specified trigger time, the current queue is considered idle.
- We then iterate through the mPendingIdleHandlers array (the elements of which are picked up in mIdleHandlers each time) to call the queueIdle method of each IdleHandler instance. If this method returns false, This instance is removed from the mIdleHandlers, meaning that the next time the queue is idle, it does not continue to call back its queueIdle method.
- After processing the IdleHandler, nextPollTimeoutMillis is set to 0, which does not block the message queue. It is important to note that this code is also not too time-consuming, since it is executed synchronously and will definitely affect subsequent message execution.
IdleHandler: queueIdle(); IdleHandler: queueIdle(); IdleHandler: queueIdle(); So how is IdleHandler used in the system source code? We can see that it is used a lot in main thread life cycle processing. For example, ActivityThread has an internal class called GcIdler that implements the IdleHandler interface, which is used to force memory GC when the main thread is idle.
final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); return false; Void doGcIfNeeded() {mGcIdlerScheduled = false; mGcIdlerScheduled = false; final long now = SystemClock.uptimeMillis(); //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime() // + "m now=" + now); if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!" ); BinderInternal.forceGc("bg"); }}Copy the code
Let’s see where it was added to the message queue:
Void scheduleGcIdler() {if (!) {void scheduleGcIdler() {void scheduleGcIdler() {void scheduleGcIdler() {if (! mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); }Copy the code
There is method in ActivityThread performLaunchActivity executes, will eventually perform to Instrumentation. The callActivityOnCreate method, in this way, It’s also useful for IdleHandler to do some extra things.
public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); activity.performCreate(icicle); postPerformCreate(activity); } private void prePerformCreate(Activity activity) { if (mWaitingActivities ! = null) { synchronized (mSync) { final int N = mWaitingActivities.size(); for (int i=0; i<N; i++) { final ActivityWaiter aw = mWaitingActivities.get(i); final Intent intent = aw.intent; if (intent.filterEquals(activity.getIntent())) { aw.activity = activity; mMessageQueue.addIdleHandler(new ActivityGoing(aw)); } } } } }Copy the code
In addition, IdleHandler is used in some third-party libraries, such as LeakCanary and Glide.
So what are some of the usage scenarios for IdleHandler for us? According to its core principle, do something when the message queue is idle, so for the main thread, we have a lot of code that does not have to follow the life cycle method synchronization, can use IdleHandler, reduce the main thread time, also reduce the application or Activity start time. For example, some third-party library initialization, especially the delay of the buried point report, etc., can be added to the message queue with IdleHandler.
Okay, here’s a question: Handler and Looper are created on the main thread. The Handler Looper is created on the main thread. The Handler Looper is created on the main thread. So why can the UI thread be permanent? Won’t be blocked? = =
Since Looper in the execution loop method is a for loop, that is, the thread will never finish executing and exit, so the APP can always be displayed. The Activity’s life cycle is to execute messages one by one through the MessageQueue, and then because of the sleep and wake mechanism of MessageQueue, When there is no message in the message queue, the message queue will sleep and release CPU resources. When a new message enters the queue, the message queue will wake up and execute the message.
Handler Application HandlerThread
HandlerThread is essentially a Thread, but the difference is that it makes full use of the Handler mechanism. By creating Looper loops internally and pushing asynchronous tasks to message queues externally, it avoids creating multiple threads repeatedly. That is, multiple asynchronous tasks can be queued for asynchronous execution. The principle is simple:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
Copy the code
Create a looper loop in the thread’s run method, so that the thread will not be destroyed if it does not quit. If there is a message, it will execute the message. If there is no message, it will release CPU resources and go to sleep according to MessageQueue sleep mechanism.
Using HandlerThread, we have noticed that, when creating the Handler to the incoming thread which is binding, so must perform HandlerThread start method, because the start method, will perform the HandlerThread run method, The Looper passed in by the Handler will not be null.
So we usually use this:
- After creating a HandlerThread, call start, and then create a Handler;
- The onLooperPrepared() method can implement this method by creating a Handler that is independent of the start position. The idea is that the run method is executed only after the start method is called.
So how do you recycle a HandlerThread? We see that there is a quit method in HandlerThread, which will eventually call MessageQueue’s Quit method to end message distribution and ultimately terminate a HandlerThread.
public boolean quit() { Looper looper = getLooper(); if (looper ! = null) { looper.quit(); return true; } return false; }Copy the code
IntentService of Handler application
IntentService is actually a combination of a Service and a HandlerThread, so we can see inside onCreate we’re creating a HandlerThread and we’re creating a Handler that’s bound to that HandlerThread, The onStat method then sends a message to the HandlerThread for execution
@Override public void onCreate() { // TODO: It would be nice to have an option to hold a partial wakelock // during processing, and to have a static startService(Context, Intent) // method that would launch the service & hand off a wakelock. super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); MServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; / / want to HandlerThread message queue messaging mServiceHandler sendMessage (MSG); }Copy the code
It is ultimately executed in the handleMessage
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 the code
So any IntentService we use must implement an abstract method called onHandleIntent in which the specific business operations are performed.
IntentService will destroy itself after executing an asynchronous task.
public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); // The answer is here: Service stopSelf(msg.arg1); }} @override public void onDestroy() {mservicelooper.quit (); }Copy the code
Handler. Post and view. post
Let’s start with a common example: get the width and height of a View.
@override protected void onCreate(Bundle savedInstanceState) {log. I ("view_w_&_h", "onCreate " + mView.getWidth() + " " + mView.getHeight()); Mview.post (new Runnable() {@override public void run() {log. I ("view_w_&_h", "onCreate postRun " + mView.getWidth() + " " + mView.getHeight()); }}); New Handler(looper.getMainLooper ()).post(new Runnable() {@override public void run() {log. I ("view_w_&_h", "onCreate Handler " + mView.getWidth() + " " + mView.getHeight()); }}); } @Override protected void onResume() { super.onResume(); / / position 4 Log. I (" view_w_ & _h ", "onResume" + mView. GetWidth () + "" + mView. GetHeight ()); New Handler(looper.getMainLooper ()).post(new Runnable() {@override public void run() {log. I ("view_w_&_h", "onResume Handler " + mView.getWidth() + " " + mView.getHeight()); }}); }Copy the code
Which of these positions can get the width and height of the mView?
We all know that the width and height of the View cannot be obtained before the View is attached to the window, because the View is not measured, layout or draw at this time, so the width and height method of the View is called directly from onCreate or onResume, which is 0. Handler.post is not available in onCreate, but it is available in onResume, and view. post is available in onCreate or onResume. Why?
Let’s look at the drawing process of a simple View:
We all know that the final traversals of the View are performed in the performTraversals() method, including measure, layout, and draw. The View is drawn in the ActivityThread’s handleResumeActivity method, which is a top-level method that calls back to the Activity’s onResume method.
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ... R = performResumeActivity(Token, clearHide, Reason); . Wm. addView(decor, L); // Call the WindowManager addView method. This is where the View is drawn. . }Copy the code
From the execution order of the code snippet above, the onStart and onResume of the Activity are executed before the drawing of the interface (decor. AddView (L) hasn’t been done yet). This explains why handler. post doesn’t get the width and height in onCreate. Because of the Handler mechanism, it pushes the message to the main thread’s message queue, so when onCreate pushes the message to the main thread’s message queue, the onResume’s message is not queued, so it’s not executed, so it’s not available. So why is onResume available? Because of the message queue mechanism, handler. post has to wait for a message to finish executing, so it has to wait for the handleResumeActivity to finish executing, and when the handleResumeActivity finishes executing, the View has been drawn, Of course you can get the width and height.
Ok, now for the second question, why does view. post get the width and height of the View in onCreate? Let’s first look at the view. post method:
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; // attachInfo is not null, indicating that the View has been attached to the window. if (attachInfo ! = null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be Successfully placed after attach. // attachInfo is null, attachInfo is not attached to window. So temporarily save the message to RunQueue getRunQueue().post(action); return true; }Copy the code
If attachInfo is null, the Runnable is temporarily stored in the RunQueue and is not executed immediately. When is the Runnable executed?
We see that HandlerActionQueue has an executeActions method, which is used to execute the Runnable stored in it:
public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; }}Copy the code
So when is this method called? Moving on: in the View dispatchAttachedToWindow method, we see that the executeActions of RunQueue are called to execute the runnable stored in the RunQueue.
void dispatchAttachedToWindow(AttachInfo info, int visibility) { ... // Transfer all pending runnables. if (mRunQueue ! = null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } onAttachedToWindow(); . }Copy the code
When is dispatchAttachedToWindow called? In view otimpl’s performTraversals method we see dispatchAttachedToWindow being executed. Host is a DecorView.
private void performTraversals() { ... host.dispatchAttachedToWindow(mAttachInfo, 0); . performMeasure(); . performLayout(); . performDraw(); }Copy the code
From the UML sequence diagram drawn from the previous View, we know that performTraversals are invoked in the handleResumeActivity of the ActivityThread.
To sum up:
In the ActivityThread handleResumeActivity method, the system will eventually switch to the ViewRootImpl performTraversals() method, The performTraversals() method calls the dispatchAttachedToWindow() method of host, which is a DecorView, The mrunqueue.executeActions () method is then called from the View’s dispatchAttachedToWindow() method, which internally iterates through the HandlerActions array, Use Handler to post previously stored Runnable.
This explains why view.post also gets the width and height of the View in onCreate, because the message view.post sends is executed after the View has been drawn.
== Some of you might ask: The dispatchAttachedToWindow method is called before the performMeasure method. Since performMeasure has not been executed at the time of the call, Why do we get the width and height after the dispatchAttachedToWindow method? = =
Back to the basic principle of the Handler mechanism, messages are stored in a queue and then wait for Loop execution. PerformTraversals are executed in a Runnable message, so when performTraversals are executed, Other messages will have to wait for performTraversals to be executed, i.e. mrunqueue.executeActions () messages will have to wait for performTraversals to be executed. So the runnable in View.post(Runnable) is executed after performTraversals, not once dispatchAttachedToWindow is called.
One question remains: when was the mAttachInfo assignment in the view. post method?
public ViewRootImpl(Context context, Display display) { ... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); . }Copy the code
We see that it is assigned in the ViewRootImpl constructor, so when was the ViewRootImpl created? Looking up, we see that it was created in the Add method of Windows ManagerGlobal.
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; . root = new ViewRootImpl(view.getContext(), display); . }Copy the code
WindowManagerGlobal’s addView method is executed in the handleResumeActivity () method of the ActivityThread. The view. post method is called after the onResume function is completed, and the message queue is pushed to the main thread for execution. If mAttachInfo is empty, it indicates that the View is not finished drawing, so it is saved temporarily and pushed to the main thread for execution after drawing.
Note that the view. post method is flawed on Android < 24, which means under 7.0.
Public Boolean POST (Runnable Action) {final AttachInfo = mAttachInfo; if (attachInfo ! = null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later // Note that here, unlike my 7.0 and above, viewrotimpl.getrunqueue ().post(action); return true; }Copy the code
Let’s look at the ViewRootImpl RunQueue implementation:
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq ! = null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; }Copy the code
In combination with ThreadLocal, it is thread-specific, meaning that stored variables are visible only to the local thread and cannot be retrieved by other threads.
Well, suppose we have a scenario where we call a view. post message in the child thread. From the above code, it will store the message in the child thread’s ThreadLocal, but when executing a RunQueue, it will look for a runnable call in the main thread, because ThreadLocal threads are isolated. The main thread never finds the message, and the message cannot be executed.
7.0 and above do not have this problem because the post method stores runnable in the main thread: getRunQueue().post(action).
To sum up:
There are two prerequisites for this problem: view.post is called in the child thread before the View is drawn. If the view. post is after the View has been drawn, that is, mAttachInfo is not empty, then the call is immediately pushed to the main thread and there is no problem with the runnable being found due to thread isolation.
By He Ying