—— 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.