Android requestLayout 跟 invalidate

Android requestLayout 和 invalidate

这两个方法很多人 搞不太清楚,这里小结一下:

View的流程图

Android requestLayout 跟 invalidate
Android requestLayout 跟 invalidate

对于标题提及的两个方法 调用invalidate()或者requestLayout()会触发哪些方法,一图道破天机。

源代码

现在来看看具体的代码: android.view.ViewRootImpl

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

这两个方法不约而同的调用了scheduleTraversals(),区别就是requsetLayout检查当前是否在主线程中,并且置位mLayoutRequsted = true,而invalidate方法没有检查。
接着我们进一步追踪下去看看scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
    }
}

这里会post一个runnable请求就是mTraversalRunnable

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

进入doTraversal()方法:

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
        try {
            performTraversals();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

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

注意这个方法中,我们重点关注performTraversals()方法:

这个方法是ViewRootImpl最复杂的方法,将近800行代码Android View 分析(下)
这篇博文表粗略的分析了这个方法,这里不打算详细分析这个方法,我们仅仅做粗线条的了解, 进一步验证博文最上面的图是否正确:
ViewRootImpl这个类的L1767行:

if (!mStopped) {
        boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
        if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

            if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="
                    + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                    + " mHeight=" + mHeight
                    + " measuredHeight=" + host.getMeasuredHeight()
                    + " coveredInsetsChanged=" + contentInsetsChanged);

             // Ask host how big it wants to be
            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;

            if (lp.horizontalWeight > 0.0f) {
                width += (int) ((mWidth - width) * lp.horizontalWeight);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                        MeasureSpec.EXACTLY);
                measureAgain = true;
            }
            if (lp.verticalWeight > 0.0f) {
                height += (int) ((mHeight - height) * lp.verticalWeight);
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                        MeasureSpec.EXACTLY);
                measureAgain = true;
            }

            if (measureAgain) {
                if (DEBUG_LAYOUT) Log.v(TAG,
                        "And hey let's measure once more: width=" + width
                        + " height=" + height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }

            layoutRequested = true;
        }
    }
}

只要没有stop 并且

if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) 

这个条件成立:
就会引发 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)

经过Debug可知:

  • requsetLayout触发performTraversal方法,进而调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),进一步的调用performLayout方法,此时layoutRequested = true

  • invalidate触发performTraversal方法,因为上面的条件不成立,所以不会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),同样也不会调用performLayout方法。

final boolean didLayout = layoutRequested && !mStopped;
boolean triggerGlobalLayoutListener = didLayout
        || mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
}

所以到这里可以确认的是:
**requsetLayout方法会调用performMeasure和performLayout方法
而invalidate方法则一个都不会调用。**
我们接着往下看是否会接着调用performDraw方法

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }

        performDraw();
    }
} 

由上面的代码可知:需满足以下几个条件才会触发performDraw方法

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) 
}

一般情况下 以上条件都会满足,除非设置了明显的标志位,所以performDraw方法会执行。

我们来大概看一下上面的几个标志位:

cancelDraw 默认是false
skipDraw 默认是false
newSurface 位于:

if (!hadSurface) {
    if (mSurface.isValid()) {
        // If we are creating a new surface, then we need to
        // completely redraw it.  Also, when we get to the
        // point of drawing it we will hold off and schedule
        // a new traversal instead.  This is so we can tell the
        // window manager about all of the windows being displayed
        // before actually drawing them, so it can display then
        // all at once.
        newSurface = true;
        mFullRedrawNeeded = true;
        mPreviousTransparentRegion.setEmpty();

        if (mAttachInfo.mHardwareRenderer != null) {
            try {
                hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
                        mSurface);
            } catch (OutOfResourcesException e) {
                handleOutOfResourcesException(e);
                return;
            }
        }
    }
} 

首先最开始的判断分支不会成立,因为 hadSurface为true,所以newSurface = false

最后一个标志位mReportNextDraw:

// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
    mReportNextDraw = true;
}

这里在执行判断该标志位之前就置位为true。所以一般会进入performDraw方法,

我们小结一下:

requsetLayout方法会触发performMeasure和performLayout,而performDraw方法是否触发视情况而定
如果以下条件满足

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) 
}

则触发performDraw方法,反之,不会触发performDraw方法。

而invalidate方法则只会触发performDraw方法,因为一般情况下条件都会满足:

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) 
}

至此,上面的流程图我们得到验证。

版权声明:本文为博主原创文章,未经博主允许不得转载。