Many developers know this knowledge point: in ActivityonCreate
In the method, we cannot directly obtain the width and height information of the View, but through(Runnable)
This method is OK. Have you ever understood the specific reasons behind it?
Readers can try the following. Can be found, except through(Runnable)
This way can obtain the true width and height of the View, and the values obtained by other methods are 0
/** * Author: leavesC * Time: 2020/03/14 11:05 * describe: * GitHub:/leavesC */ class MainActivity : AppCompatActivity() { private val view by lazy { findViewById<View>() } override fun onCreate(savedInstanceState: Bundle?) { (savedInstanceState) setContentView(.activity_main) getWidthHeight("onCreate") { getWidthHeight("") } Handler().post { getWidthHeight("handler") } } override fun onResume() { () getWidthHeight("onResume") } private fun getWidthHeight(tag: String) { (tag, "width: " + ) (tag, "height: " + ) } }
E/onCreate: width: 0 E/onCreate: height: 0 E/onResume: width: 0 E/onResume: height: 0 E/handler: width: 0 E/handler: height: 0 E/: width: 263 E/: height: 263
This leads to several questions:
-
(Runnable)
Why can you get the true width and height of the View -
(Runnable)
and(Runnable)
What's the difference - exist
onCreate
、onResume
Why can't you directly get the true width and height of the View in the function? -
(Runnable)
Who executes the Runnable in it? Can it be guaranteed to be executed?
Let’s answer these questions one by one. This article is based on Android API 30 for analysis.
1. (Runnable)
Take a look(Runnable)
The method signature of Runnable can be seen in two types:
- if
mAttachInfo
Not null, then the Runnable is handed over tomAttachInfo
Internal Handler handles - if
mAttachInfo
null, the Runnable is handed over to HandlerActionQueue for processing
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return (action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; } private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
1、AttachInfo
Let's take a look first(Runnable)
The first processing logic
AttachInfo is a static class inside the View, which holds a Handler object inside. From the comments, it can be seen that it is provided by ViewRootImpl.
final static class AttachInfo { /** * A Handler supplied by a view's {@link }. This * handler can be used to pump events in the UI events queue. */ @UnsupportedAppUsage final Handler mHandler; AttachInfo(IWindowSession session, IWindow window, Display display, ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer, Context context) { ··· mHandler = handler; ··· } ··· }
FindmAttachInfo
The assignment time can be traced to the ViewdispatchAttachedToWindow
Method, the method is called means that the View has been Attached to the Window
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ··· }
Search againdispatchAttachedToWindow
The method is called and can be tracked to the ViewRootImpl class. ViewRootImpl contains a Handler objectmHandler
, and in the constructormHandler
Initialize as one of the constructor parametersmAttachInfo
. ViewRootImpl'sperformTraversals()
The method will call the dispatchAttachedToWindow method of DecorView and pass in mAttachInfo, thereby calling all Views in the entire view tree layer by layerdispatchAttachedToWindow
Method so that all childViews can be obtainedmAttachInfo
Object
final ViewRootHandler mHandler = new ViewRootHandler(); public ViewRootImpl(Context context, Display display, IWindowSession session, boolean useSfChoreographer) { ··· mAttachInfo = new (mWindowSession, mWindow, display, this, mHandler, this, context); ··· } private void performTraversals() { ··· if (mFirst) { ··· (mAttachInfo, 0); ··· } ··· performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, mWidth, mHeight); performDraw(); ··· }
also,performTraversals()
The method is also responsible for starting the Measure, Layout, and Draw processes of the entire view tree, only whenperformLayout
Only after being called can the View determine its width and height information. andperformTraversals()
It is also called by ViewRootHandler, that is, the drawing task of the entire view tree is first inserted into the MessageQueue, and then the main thread takes out the task for execution. Since the messages inserted into the MessageQueue are executed sequentially by the main thread,(action)
This ensures that action will not be called after performTraversals is executed, so we can get the true width and height of the View in Runnable
2、HandlerActionQueue
Let's see again(Runnable)
The second processing logic
HandlerActionQueue can be regarded as a task queue specially used to store Runnables.mActions
All Runnables to be executed and the corresponding delay time are stored. twopost
The method is used to save the Runnable object to be executed into mActions.executeActions
Be responsible formActions
All tasks in the Handler are submitted to the Handler for execution
public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; public void post(Runnable action) { postDelayed(action, 0); } public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = (mActions, mCount, handlerAction); mCount++; } } 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]; (, ); } mActions = null; mCount = 0; } } private static class HandlerAction { final Runnable action; final long delay; public HandlerAction(Runnable action, long delay) { = action; = delay; } public boolean matches(Runnable otherAction) { return otherAction == null && action == null || action != null && (otherAction); } } ··· }
So,getRunQueue().post(action)
Just save the Runnable object we submitted tomActions
In the meantime, it also requires active external callsexecuteActions
Methods to execute tasks
This operation that actively executes tasks is also from ViewdispatchAttachedToWindow
to complete, so thatmActions
All tasks in themHandler
In the MessageQueue, wait until the main thread completes executionperformTraversals()
The method will be executed afterwardsmActions
, so at this time we can still obtain the true width and height of the View
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ··· // Transfer all pending runnables. if (mRunQueue != null) { (); mRunQueue = null; } ··· }
2. (Runnable)
(Runnable)
and(Runnable)
What's the difference?
From the above source code analysis, you can know that(Runnable)
The reason why you can get the true width and height of the View is mainly because the operation of obtaining the width and height of the View must be executed after the View is drawn, and(Runnable)
The reason why it doesn't work is that it cannot guarantee this
Although these two post(Runnable) operations are inserted into the same MessageQueue, they are eventually handed over to the main thread for execution. However, the task of drawing the view tree is submitted only after onResume is called back, so the task we submit with Handler in onCreate will be earlier than the task of drawing the view tree, so we cannot get the true width and height of the View
3. onCreate & onResume
existonCreate
、onResume
Why can't we directly obtain the true width and height of the View in the function?
Referring back to the reason from the result, which shows that when onCreate and onResume are called back, ViewRootImpl'sperformTraversals()
The method has not been executed yet, so when is the specific execution time of the performTraversals() method?
This can be found on the call chain ActivityThread -> WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl
First, ActivityThreadhandleResumeActivity
The method is responsible for callback and back activitiesonResume
Method, and if the current Activity is first started, a DecorView is added to the ViewManager (wm)
@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ··· //Activity's onResume method final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ··· if ( == null && ! && willBeVisible) { ··· ViewManager wm = (); if () { if (!) { = true; //Focus (decor, l); } else { (l); } } } else if (!willBeVisible) { if (localLOGV) (TAG, "Launch " + r + " mStartedActivity set"); = true; } ··· }
The specific implementation class of ViewManager here is WindowManagerImpl. WindowManagerImpl will forward the operation to WindowManagerGlobal
@UnsupportedAppUsage private final WindowManagerGlobal mGlobal = (); @Override public void addView(@NonNull View view, @NonNull params) { applyDefaultToken(params); (view, params, (), mParentWindow, ()); }
WindowManagerGlobal will complete the initialization of ViewRootImpl and call itsetView
Method, the method will be called againperformTraversals
Method starts the drawing process of view tree
public void addView(View view, params, Display display, Window parentWindow, int userId) { ··· ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ··· root = new ViewRootImpl((), display); (wparams); (view); (root); (wparams); // do this last because it fires off messages to start doing things try { (view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
So,performTraversals
The method is called atonResume
After the method, weonCreate
andonResume
The actual width and height of the View cannot be obtained in the function. Of course, when the Activity is called the second time in a single lifecycleonResume
When you are using the method, you can naturally obtain the width and height attribute of the View
4. (Runnable) compatibility
From the above analysis, we can draw a conclusion:(Runnable)
In the end, tasks are inserted into the MessageQueue associated with the main thread and finally executed sequentially by the main thread, so even if we call in the child thread(Runnable)
, you can finally get the correct width and height value of the View
However, this conclusion is only valid on API 24 and later versions.(Runnable)
The method also has a version compatibility problem, and there are different implementation methods on API 23 and previous versions.
//Android API 24 and later versions public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return (action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; } //Android API 23 and previous versions public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return (action); } // Assume that post will succeed later ().post(action); return true; }
On Android API 23 and previous versions,attachInfo
When null, the Runnable is saved to a static member variable inside ViewRootImplsRunQueues
middle. andsRunQueues
The RunQueue is saved internally through ThreadLocal, which means that the RunQueue obtained by different threads are different objects, which also means that if we call it in the child thread(Runnable)
If the method is used, the Runnable will never be executed because the main thread cannot get the RunQueue of the child thread at all.
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); static RunQueue getRunQueue() { RunQueue rq = (); if (rq != null) { return rq; } rq = new RunQueue(); (rq); return rq; }
In addition, sincesRunQueues
It is a static member variable, and the main thread will always correspond to the same RunQueue object. If we call it in the main threadIf the (Runnable) method is used, the Runnable will be added to the RunQueue associated with the main thread, and the subsequent main thread will take out the Runnable to execute
Even if the View is an object we directly new (like the following example), the above conclusion still takes effect. When the system needs to draw other views, the task will be taken out, and it will usually be executed soon. Of course, since the View does not have AttachedToWindow at this time, the width and height value obtained must be 0
val view = View(Context) { getWidthHeight("") }
right(Runnable)
The compatibility issues of the method are summarized:
- When API < 24, if it is called in the main thread, the submitted Runnable will be executed regardless of whether the View has AttachedToWindow. But the true width and height of the View can only be obtained if the View is AttachedToWindow
- When API < 24, if it is called in the child thread, the submitted Runnable will never be executed regardless of whether the View has AttachedToWindow or not
- When API >= 24, no matter whether it is called in the main thread or the child thread, as long as the View is AttachedToWindow, the submitted Runnable will be executed, and the true width and height value of the View can be obtained. If it is not AttachedToWindow, Runnable will never be executed
The above is the detailed content of the principles and defects of Android. For more information about Android, please follow my other related articles!