61

requestLayout()的执行流程

 5 years ago
source link: http://www.10tiao.com/html/272/201806/2666454227/2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

每次更新View都会想到requestLayout()方法,所以想看看它的流程是怎样的。
我一直觉得,先理清楚步骤再去看源码会好很多,所以先给出requestLayout()的一个调用流程:

  • View#requestLayout()

  • ViewGroup#requestLayout()

  • ViewRootImpl#requestLayout()

  • ViewRootImpl#scheduleTraversals()

  • ViewRootImpl#doTraversal()

  • ViewRootImpl#performTraversals()

最后ViewRootImpl#performTraversals()会依次调用performMeasure() , performLayout() , performDraw()。

requestLayout()是在View中定义的,View#requestLayout()

public void requestLayout() {    if (mMeasureCache != null) mMeasureCache.clear();    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();        if (viewRoot != null && viewRoot.isInLayout()) {            if (!viewRoot.requestLayoutDuringLayout(this)) {                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }    /**记住这里设置的标志位,非常重要...**/
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;    if (mParent != null && !mParent.isLayoutRequested()) {        /**画重点,这里调用父类的requestLayout,一直往上循环...**/
        mParent.requestLayout();
    }    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

ViewParent是一个接口,定义了view的一些操作等方法。ViewGroup就是一个ViewParent,所以mParent.requestLayout会一直往上遍历,而终点是ViewRootImpl,ViewRootImpl#requestLayout()

// 入口方法,接下来执行 scheduleTraversals();public void requestLayout() {    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}// 会执行mTraversalRunnable,它是一个Runnablevoid scheduleTraversals() {    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}// 诺,TraversalRunnable 的定义。所以最后执行了doTraversal();final class TraversalRunnable implements Runnable {    @Override
    public void run() {
        doTraversal();
    }
}// 重点还不在这,重点是 performTraversals();!!!!void doTraversal() {    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        performTraversals();
        ......
    }
}

performTraversals()真的是一个很长很长的方法...,我平时写的一个类一般都不会有这么多。所以非常难去理解里面到底做了什么,所以我只是看了个大概的流程。

private void performTraversals() {    final View host = mView;    if (host == null || !mAdded)        return;
        ...... // 此处省略了很多行。。。
       
        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);                /**终于出现了:performMeasure**/
                // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ......
            }
        }
    } else {
        maybeHandleWindowMove(frame);
    }    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;    if (didLayout) {        /**这里是performLayout**/
        performLayout(lp, mWidth, mHeight);
        ......
    }
    ...... // 此处省略了很多行。。。
    if (!cancelDraw && !newSurface) {        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }        /**这里是performDraw()**/
        performDraw();
    } else {
        ......
    }
    mIsInTraversal = false;
}

本来想看看performTraversals()具体执行了些什么,后来我放弃了...,我只是找出了跟View绘制相关的三个方法:performMeasure() performLayout() performDraw()。
ViewRootImpl#performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {    if (mView == null) {        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

直接就调用了view的measure方法,从ViewRootImpl回到了View。View#measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......    /**还记得requestLayout方法里有把mPrivateFlags  = PFLAG_FORCE_LAYOUT**/
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;    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);    /**依据forceLayout 和needsLayout决定是否要执行onMeasure**/
    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
            /**出现了onMeasure**/
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } 
        /**记住这里设置的标识位**/
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        ......
}

调用requestLayout时,设置了标识位mPrivateFlags  = PFLAG_FORCE_LAYOUT,最终这个view会根据mPrivateFlags 来判断是否要执行onMeasure方法。
那么最后还mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;,这个接下来onLayout会需要。
ViewRootImpl#performLayout()

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,        int desiredWindowHeight) {
    ......    final View host = mView;    if (host == null) {        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ......
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

host是view,那么performLayout就执行了view的layout(),View#layout()

public void layout(int l, int t, int r, int b) {
    ......    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
       ......
    }
    ......
}

这里根据条件执行onLayout,上一步执行measure时,mPrivateFlags |= PFLAG_LAYOUT_REQUIRED。
ViewRootImpl#performDraw()

private void performDraw() {
    ......
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}// 省略了很多行的代码,心好累。。。private void draw(boolean fullRedrawNeeded) {
    ......
    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        ......
        } else {
            ......            /**你敢信onDraw的逻辑竟然隐藏在这里?**/
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {                return;
            }
        }
    }
    ......
}private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,        boolean scalingRequired, Rect dirty) {    final Canvas canvas;    try {
        .....        // 意外找到了Canvas的赋值地点
        canvas = mSurface.lockCanvas(dirty);
        .....
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);        return false;
    } catch (IllegalArgumentException e) {
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    }    try {
        ......        try {
            ......            /**终于找到你**/
            mView.draw(canvas);
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
           ......
        }
    } finally {
       ......
    }    return true;
}

因此,逻辑是:performDraw()->draw()->drawSoftware()->view.draw()。
View#draw()

public void draw(Canvas canvas) {    final int privateFlags = mPrivateFlags;    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    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 (绘制子view)    *      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;    if (!dirtyOpaque) {
        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
        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children
        dispatchDraw(canvas);
        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);        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);        if (debugDraw()) {
            debugDrawFocus(canvas);
        }        // we're done...
        return;
    }

注释真好,这个方法的逻辑一下子就看懂了。

总结

  1. 当我们调用requestLayout时,会执行parent的requestLayout,最终执行到ViewRootImpl的requestLayout。

  2. ViewRootImpl经过一系列方法的调用执行performTraversals()方法。

  3. performTraversals()会依次执行performMeasure() performLayout() performDraw()方法。

看起来很简单,但真的会被源码转晕。而且实际调试过程中发现,View的draw()方法不一定是drawSoftware()执行的,可以看看ThreadedRenderer#draw()
mPrivateFlags 挺重要的,会根据它的值来决定一些方法是否要调用。

其实忽略了的代码中,隐藏着很重要的逻辑,主要是对draw()方法的执行判断。因为代码通篇看下来执行requestLayout后,onMeasure(),onLayout(),onDraw()方法会依次执行,onMeasure()和onLayout()执行是可以肯定的。但是onDraw()就不一定了。这部分我还没看懂,果然源码不简单。。。



作者:zcbiner
链接:https://www.jianshu.com/p/c25b497df8be
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

                        喜欢 就关注吧,欢迎投稿!




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK