@linux1s1s
2019-02-15T07:12:38.000000Z
字数 5413
阅读 6407
AndroidView 2015-07
这两个方法很多人 搞不太清楚,这里小结一下:

对于标题提及的两个方法 调用invalidate()或者requestLayout()会触发哪些方法,一图道破天机。
现在来看看具体的代码: android.view.ViewRootImpl
@Overridepublic 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 {@Overridepublic 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 beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);// Implementation of weights from WindowManager.LayoutParams// We just grow the dimensions as needed and re-measure if// needs beint 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)}
至此,上面的流程图我们得到验证。
