Source Code Analysis for View's requestLayout() Method

;

First, let's look at what the requestLayout() method does.
View#requestLayout():

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * <p>Subclasses which override this method should call the superclass method to
     * handle possible request-during-layout errors correctly.</p>
     */
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
		// AttachInfo When a View is attached to its parent Window, the set of information that the parent Window provides to the View is generally not null
		// mViewRequestingLayout is used when requestLayout() is called during layout to track which View initiated the requestLayout() request with a default value of null
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            // Determine if the current View tree is in the layout process
            if (viewRoot != null && viewRoot.isInLayout()) {
            	// Call requestLayoutoutDuringLayout(this) if you are laying out to delay this layout
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            // mViewRequestingLayout is assigned to the View that currently initiates requestLayout()
            mAttachInfo.mViewRequestingLayout = this;
        }
		// Set two flags for mPrivateFlags, two important ones
		// PFLAG_FORCE_LAYOUT, which is known by the meaning of the surface as a layout marker, executes the View's mearsure() and layout() methods
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
        	// Important code, call the parent's requestLayout here, and loop up...
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        	// mViewRequestingLayout clears the previous assignment and resets it to null
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

The English comment for the method roughly means that invoking the method will adjust the layout of the View tree when the View changes so that the layout of the View is invalid.This method should not be called when the hierarchy of the View is in the layout.If the View tree is being laid out, the request will be executed when the current layout process is completed, or after the drawing process is completed and the next layout is started.
Note: Subclasses that override this method should call the parent's method to properly handle possible errors during the request layout;

		// Important code, call the parent's requestLayout here, and loop up...
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }

This is the most important judgment. If the parent container is not empty and the parent container does not call the parent container's requestLayout() without LayoutRequest, since the parent container is ViewGroup and does not override requestLayout(), but ViewGroup's parent, ViewGroup's parent, is also View, calls its parent container's requestLayout(), which is constantly uploaded and PFLAG_set for the parent containerFORCE_LAYOUT and PLLAG_The INVALIDATED flags end up in the top-level outermost container, DecorView, where the mParent of DecorView is a ViewRootImpl object (why is the mParent of DecorView a ViewRootImpl?Answers may refer to the previous article Source Code Analysis for invalidate() Method ), it also sets two flags, and then calls ViewRootImpl#requestLayout().
ViewRootImpl#requestLayout():

    @Override
    public void requestLayout() {
    	// mHandlingLayoutInLayoutrequest is a boolean type
    	// Set to true in performLayout, meaning that you are not currently in the Layout process, that is, you are currently false
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // Focus: Go back to this familiar method
            scheduleTraversals();
        }
    }

ViewRootImpl#scheduleTraversals():

    @UnsupportedAppUsage
    void scheduleTraversals() {
    	// Notice this flag bit and call requestLayout several times, only if it is false
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // Setting the synchronization barrier for Handler messages through postSyncBarrier()
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Handler sends messages, mTraversalRunnable with synchronization barrier is preferred, next blog post tracks why it is preferred
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

ViewRootImpl#TraversalRunnable:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
        	// TraversalRunnable takes precedence over previous synchronization barriers
            doTraversal();
        }
    }

ViewRootImpl#doTraversal():

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // Remove the synchronization barrier for Handler messages set through postSyncBarrier()
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
			// This is where many blogs choose to enter when they start analyzing the drawing process of View
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

PerfmTraversals () is a very important method. There are many methods. To put it simply, we need to focus on the following methods:
ViewRootImpl#performTraversals() :

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        mIsInTraversal = true;
        ......Omit Code
        
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
            
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    // Ask host how big it wants to be
                    // Concern Method 1 Performance Measure Measurement Method
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
					......Omit Code
					// Use measureAgain to determine if it is necessary to measure again
                    if (measureAgain) {
                    	// Concern Method 1 Performance Measure Measurement Method
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
                    layoutRequested = true;
                }
            }
        } else {
            maybeHandleWindowMove(frame);
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
        	// Focus method 2 performLayout placement method
            performLayout(lp, mWidth, mHeight);
            ......Omit Code
        }
        ......Omit Code
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            // Concern method 3 performDraw drawing method
            performDraw();
        } else {
            ......Omit Code
        }
        mIsInTraversal = false;
    }

Method 1:
ViewRootImpl#performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) :

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
        	// Call the View's measure method, which in turn calls the View's onMeasure method to measure the View
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

View#measure(int widthMeasureSpec, int heightMeasureSpec):

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
		......Omit Code
		// The mPrivateFlags flag bit is set to PFLAG_in the requestLayout() methodFORCE_LAYOUT, so forceLayout is true at this point
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
		// It is forceLayout and needsLayout that determine whether to execute the onMeasure() method
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                // View calls the onMeasure method to measure itself
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
			// Set mPrivateFlags identity bit
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
        ......Omit Code
    }

In this method, because the identity bit mPrivateFlags = PFLAG_is set when the requestLayout() method is calledFORCE_LAYOUT, and eventually this View will use mPrivateFlags to determine if the onMeasure method of View is to be executed.
Method Last Set mPrivateFlags |= PFLAG_LAYOUT_REQUIRED, which is needed by the onLayout method of View.

Concern method 2:
ViewRootImpl#performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight):

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
  
        try {
        	// host is the View that currently calls the requestLayout() method, that is, the layout method of View is called
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                ......Omit Code
                if (validLayoutRequesters != null) {
             		......Omit Code
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    // host is the View that currently calls the requestLayout() method, that is, the layout method of View is called
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    ......Omit Code
                }
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

View#layout(int l, int t, int r, int b):

    public void layout(int l, int t, int r, int b) {
		......Omit Code
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
		// Set the mPrivateFlags token bit to PFLAG_in View's measure methodLAYOUT_REQUIRED, so if is true at this point
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        	// View calls the onLayout method to adjust its placement
            onLayout(changed, l, t, r, b);
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
			// Set mPrivateFlags identity bit
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            ......Omit Code
        }
        ......Omit Code

Concern method 3:
ViewRootImpl#performDraw() :

private void performDraw() {
		......Omit Code
        try {
        	// Call View#draw(boolean fullRedrawNeeded)
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

View#draw(boolean fullRedrawNeeded):

private void draw(boolean fullRedrawNeeded) {
    ......Omit Code
    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        ......Omit Code
        } else {
            ......Omit Code
            // Call View#drawSoftware(Surface surface,......)
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    ......Omit Code
}

View#drawSoftware(Surface surface, ...):

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
    try {
        .....Omit Code
        // Unexpected location of Canvas assignment
        canvas = mSurface.lockCanvas(dirty);
        .....Omit Code
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    }
    try {
        ......Omit Code
        try {
            ......Omit Code
            // The current View calls draw(Canvas canvas) to draw the View
            mView.draw(canvas);
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
           ......Omit Code
        }
    } finally {
       ......Omit Code
    }
    return true;
}

View#draw(Canvas canvas):

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */
    // Step 1, draw the background, if needed
    int saveCount;
    drawBackground(canvas);
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content to draw its own content
        onDraw(canvas);

        // Step 4, draw the child ren to draw the child View
        dispatchDraw(canvas);
        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;
        ......Omit Code
        canvas.restoreToCount(saveCount);
        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    }

The ViewRootImpl#performTraversals() method may in turn call performMeasure, performLayout, and performDraw.After the trace code looks like requestLayout(), the onMeasure(), onLayout(), onDraw() methods execute in turn, and onMeasure() sets the mPrivateFlags token to PFLAG_because of requestLayout().FORCE_LAYOUT, so that the judgement is true, so it will certainly be executed; onLayout () because the mPrivateFlags bit is set to PFLAG_in onMeasure ()LAYOUT_REQUIRED, makes the judgment true, so it will certainly be executed; but onDraw () may not, interesting children's shoes, can track how TextView readjusts its layout after setText.

TextView#checkForRelayout():

private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
			......Omit Code
            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
			......Omit Code
            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

In this method, the invalidate() method is called immediately after the requestLayout() method is called.
My understanding is that the requestLayout() method will certainly go to onMeasure() and onLayout(), whereas the invalidate() method will certainly execute the onDraw() method, which is equivalent to adding a double insurance policy.

Personal understanding, if there is a god pointing out the discussion, we still hope to give advice!!!

At this point, the source code flow analysis for the View#requestLayout() method is complete;

Posted by yaron on Sun, 14 Jun 2020 17:10:17 -0700