—— view.post ()
Cause: A group of guys asked why handler.post () is executed after the Activity’s onResume(). After searching, I couldn’t find the reason. After view.post(), the width and height of the view can be accurately obtained.
🤔️: view.post () why is the width and height of the View accurately obtained?
public boolean post(Runnable action) {
// Comment 1: Call attachInfo internal handler.post () if attachInfo is not empty
// This raises the question of where attachInfo is assigned. This question is in doubt.
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
return attachInfo.mHandler.post(action);
}
// Comment 2: attachInfo is empty at this point
getRunQueue().post(action);
return true;
}
Copy the code
Question 1: Where is attachInfo assigned?
GetRunQueue ().post(action); Who is it?
private HandlerActionQueue getRunQueue(a) {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
Copy the code
That’s pretty simple, so now we need to see what HandlerActionQueue() is.
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
/ / comment 1
public void post(Runnable action) {
postDelayed(action, 0);
}
/ / comment 2
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; }}public void removeCallbacks(Runnable action) {}
// Pretend not to know this is the execution method
public void executeActions(Handler handler) {}
public int size(a) {}
public Runnable getRunnable(int index) {}
public long getDelay(int index) {}
private static class HandlerAction {}}Copy the code
Note 1: The post() method is getRunQueue().post(action); It calls comment 2 internally which is postDelayed() comment 2 a quick look at the code and it turns out that the logic is basically just to cache our post runnable, so where do we actually execute the runnable? I realized that we followed it all the way up to the postDelayed() method and we didn’t see the corresponding execution so we blew it up
Shadow · Secret! Go back in time.
Let’s just look at where the mRunQueue returned from getRunQueue() is called.
// Don't upload pictures... Don't know how to control the size so use code blocks
private HandlerActionQueue getRunQueue(a) {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// Ignore some code
if(mRunQueue ! =null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null; }}Copy the code
Checking the reference to mRunQueue shows that these are the only two methods used in the View code, so we’ve already seen the first method so we’ll focus on the second method.
The second method above shows that mAttachInfo is assigned to info within dispatchAttachedToWindow(AttachInfo info, int visibility). Let’s keep going.
You can see from the call relationship to dispatchAttachedToWindow() that the following methods are called
private void performTraversals(a) {
// cache mView since it is used so much below...
finalView host = mView; /... // / comment 1
host.dispatchAttachedToWindow(mAttachInfo, 0); /... / performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);// Perform the measurement
performLayout(lp, mWidth, mHeight);// Execute the layout
performDraw();// Perform the drawing
}
Copy the code
You can see that this method is called in host and passed in mAttachInfo as a parameter, and that host is a DecorView
Why DecorView? We can go back and verify that from the source we know that host is a member variable of mView in ViewRootImpl. If we check the assignment to mView we can see the following code:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;// Assign to mView/... /}}}Copy the code
So when can I call setView()? You can see that setView() is not a static method, so you need to call it and reference the instance.
So we can look at the constructor
public ViewRootImpl(Context context, Display display) { mContext = context; /... // / comment 1
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
Copy the code
If you look at the code in comment 1, you can see that mAttachInfo was created in the ViewRootImpl constructor.
If we look at the constructor call again, we can see that the WindowManagerGlobal addView() method creates the ViewRootImpl,
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
/.../
root = new ViewRootImpl(view.getContext(), display);
}
Copy the code
The Windows ManagerGlobal addView() method is called by WindowManagerImpl addView()
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code
You can see this by calling this method again
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView(); / / comment 1
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if(impl ! =null) { impl.notifyChildRebuilt(); }}if (a.mVisibleFromClient) {
if(! a.mWindowAdded) { a.mWindowAdded =true;
wm.addView(decor, l); / / comment 2
} else{ a.onWindowAttributesChanged(l); }}}else if(! willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true; }}Copy the code
This can be done at comment 1: a DecorView is returned and an entry is added at comment 2. At this point we know that the mView at ViewRootImpl is a DecorView.
Knowledge: And we can see from the code above that the view is added to the window only when onResume is used and the view’s measured layout is drawn. This is why onCreate() gets the view width and height 0, because the view is not added to the window at that time!!
Time again travels back to performTraversals() of ViewRootImpl
Now that we know that mView is a DecorView, this DecorView is a ViewGroup that descends from FrameLayout, We find no handling of the dispatchAttachedToWindow() method in the DecorView and FrameLayout, so we end up in the ViewGroup.
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// Iterate through the call so that it will eventually return to the View's dispatchAttachedToWindow()child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); }}Copy the code
At this point we return to mrunqueue.executeActions (info.mhandler) within the View’s dispatchAttachedToWindow() method; And click to see the source code
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;/ / comment 1
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);/ / comment 2
}
mActions = null;
mCount = 0; }}Copy the code
From comment 1 we can see that mActions is actually a storage array of runnable that we passed in with the view.post method. Comment 2 passes the runnable to handler.post() and adds it to the MessageQueue inside the Looper held by this handler.
At this point we can make a general summary.
Conclusion:
1: the View of internal mAttachInfo will in the View of dispatchAttachedToWindow () method of the assignment in dispatchDetachedFromWindow () value is null, and mAttachInfo is in ViewRootImpl constructor, so we know that the view has been added to the window when its attchInfo is not null, if it is null then the view is not inside the window.
2: We get the view’s width and height correctly via view.post() mainly because the Android lifecycle is driven by handlers, so when a ViewRootImpl is created within the Activity’s onResume() lifecycle, The Handler for the main thread is executing the process of processing a Message, Although we can see from the above ViewRootImpl performTraversals() source code that the view cache runnable will be in performMeasure(), Methods like performLayout() and performDraw() are posted and added to the MessageQueue list, but the runnable belongs to the next Message, The three methods performMeasure(), performLayout() and performDraw() belong to the logic of this Message. Looper inside the Handler will process the next Message only after the Message processing is completed. This ensures that view.post () gets the View’s width and height correctly.