本文主要探讨能够触发performTraversals()执行的invalidate()、postInvalidate()和requestLayout()方法的流程。invalidate()和postInvalidate()能够触发View的重画,这两个方法最终会调用到performTraversals()中的performDraw()来完成重绘制,但是是否会执行onMeasure()和onLayout()过程要根据标志位的状况来决定;requesetLayout()方法也会调用到performTraversals()方法,但是只会执行measure和layout流程,不会调用到draw流程来触发重画动作。
一、requestLayout()流程
直接来看View.requestLayout()代码。
@CallSuper public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); //如果当前的整个View树在进行布局流程的话,则会调用requestLayoutDuringLayout() //让这次的布局延时执行 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; } //PFLAG_FORCE_LAYOUT会在执行View的measure()和layout()方法时判断 //只有设置过该标志位,才会执行measure()和layout()流程 mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }
该方法主要是设置了PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED到当前View的Flag中,然后调用到当前View(当前View可能是一个控件View,也可能是一个布局View,因为对于这两类View都能调用requestLayout()方法)的父布局View的requestLayout()方法,父布局View是ViewGroup类型,没有重写该requestLayout()方法,所以实际还是调回到View.requestLayout()方法的这套逻辑。这个过程,就是设置当前View标志位后,就不断的向上调用父布局View的requestLayout(),最后调用到根View即DecorView的requestLayout(),而DecorView的mParent变量指向的是当前窗口对应的ViewRootImpl对象,最后一次设置完DecorView标志位后,调用到ViewRootImpl.requestLayout()方法,进入该代码。
@Override public void requestLayout() { //该boolean变量会在ViewRootImpl.performLayout()开始时置为ture,结束置false //表示当前不处于Layout过程 if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
如果当前不是正在执行layout过程,则会调用scheduleTraversals()方法,进入ViewRootImpl.scheduleTraversals()。
void scheduleTraversals() { if (!mTraversalScheduled) { //在下一段代码处会置回false //表示在排好这次绘制请求前,不再排其它的绘制请求 mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
这里主要是调用到了ViewRootImpl的另一个重要的变量mChoreographer,它是Choreographer类型的,这个对象会请求Vsync信号来控制绘制的进行,实现了按帧进行绘制的机制,这个类会在后面的文章进行介绍。该方法对于绘制的请求经过了Choreographer的编排后,最终会调用回ViewRootImpl.doTraversal()方法。
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); ... //用于调试相关代码 performTraversals(); ... //用于调试相关代码 } }
然后调用到ViewRootImpl.performTraversals()方法。
二、invalidate()与postInvalidate()流程
invalidate()与postInvalidate()都是用于被调用来触发View的更新(重画)动作,区别在于invalidate()方法是在UI线程自身中使用,而postInvalidate()是非UI线程中使用。 首先来看View.postInvalidate()。
public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there‘s no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } }
调用到了对应的ViewRootImpl对象的dispatchInvalidateDelayed()方法,进入该代码。
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }
这里实现了一个消息机制,发送了MSG_INVSLIDSTE。进入处理消息的ViewRootImpl.handleMessage()方法。
@Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ... }
这里实际上就是调回了调用postInvalidate()方法的View的invalidate()方法。由于invalidate()方法只能在UI线程执行,所以postInvalidate只是实现了一个消息机制,让用户能够在非UI线程使用,最终还是调用到invalidate()方法来触发重画,实现界面更新动作。继续来看View.invalidate()方法,该方法逻辑的实际实际上时调用到invalidateInternal()方法来实现的。
public void invalidate() { invalidate(true); } void invalidate(boolean invalidateCache) { //mLeft、mRigth、mTop、mBottom记录的是当前View边界距离其父布局View边界的距离 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } //如果当前视图为不可见状态且没有动画正在执行,且其父布局也没有过渡动画执行,则跳过 if (skipInvalidate()) { return; } //当前View没有正在执行该方法 //或绘制缓存可用或未重绘过或透明度发生改变 //PFLAG_DRAWN会在该方法内去改标志位 //PFLAG_INVALIDATED会在View.draw()方法执行时去掉该标志位 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { //如果需要全部重绘,invalidate()未传参调用时默认为true if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Propagate the damage rectangle to the parent view. //damage记录的区域是需要更新的dirty区域,当前的坐标时相对于自身来设置的 //通过不断调用到父类的invalidateChild()方法,来不断更新dirty区域的相对坐标 final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } // Damage the entire projection receiver, if necessary. if (mBackground != null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver != null) { receiver.damageInParent(); } } // Damage the entire IsolatedZVolume receiving this view‘s shadow. if (isHardwareAccelerated() && getZ() != 0) { damageShadowReceiver(); } } }
这里会通过调用mParent的invalidateChild()方法,来触发父类对于dirty区域的调整(可能会调整可能还是原区域)及改区域相对坐标的调整。进入ViewGroup.invalidateChild()方法。
@Override public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes // through //drawAnimation记录调用该方法的子View是否正在执行动画 final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION; // Check whether the child that requests the invalidate is fully opaque // Views being animated or transformed are not considered opaque because we may // be invalidating their old position and need the parent to paint behind them. //调用该方法的子View是否不透明:处于不透明状态且没有在执行动画且变化矩阵没有变化 //Matrix可以用于View的平移、缩放、扩放、旋转等操作,比如某些应用上的双指缩放功能 Matrix childMatrix = child.getMatrix(); final boolean isOpaque = child.isOpaque() && !drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); // Mark the child as dirty, using the appropriate flag // Make sure we do not set both flags at the same time int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY; if (child.mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } final int[] location = attachInfo.mInvalidateChildLocation; //记录子View边界距离父View左边界和上边界的距离到Location中,用于下一段代码中的计算 location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; //如果子View设置了变换矩阵,则根据变换矩阵调整dirty区域 if (!childMatrix.isIdentity() || (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); Matrix transformMatrix; if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { Transformation t = attachInfo.mTmpTransformation; boolean transformed = getChildStaticTransformation(child, t); if (transformed) { transformMatrix = attachInfo.mTmpMatrix; transformMatrix.set(t.getMatrix()); if (!childMatrix.isIdentity()) { transformMatrix.preConcat(childMatrix); } } else { transformMatrix = childMatrix; } } else { transformMatrix = childMatrix; } transformMatrix.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } //这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环 do { View view = null; //parent可能为ViewGroup类型,也可能为ViewRootImpl类型 //最后一次循环执行时为ViewRootImpl类型 if (parent instanceof View) { view = (View) parent; } //如果子View正在执行动画,设置遍历的父布局View的动画标识 if (drawAnimation) { if (view != null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } } // If the parent is dirty opaque or not dirty, mark it dirty with the opaque // flag coming from the child that initiated the invalidate //设置当前ViewGroup的Dirty标识,表示当前的ViewGroup需要重绘 if (view != null) { if ((view.mViewFlags & FADING_EDGE_MASK) != 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } //调用当前布局View的invalidateChildParent()方法,返回的值为当前布局View的父布局 //通过循环向上调用,最后返回的根布局是ViewRootImpl对象 parent = parent.invalidateChildInParent(location, dirty); if (view != null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } } } while (parent != null); } }
在do-while循环中会调用到parent = parent.invalidateChildInParent(location, dirty),这里执行到ViewGroup.invalidateChildInParent()方法。
@Override public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { // if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { //如果ViewGroup有没有动画执行或者动画已经完成 if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { //dirty记录的是最开始调到invalidate()的View的区域 //dirty的四个坐标值值在执行下面代码是相对于当前循环到上一个ViewGroup来确定的 //这里做了一个偏移动作,偏移的量是当前上一个ViewGroup相对于现在ViewGroup的偏移值 //做完下面的偏移操作后,dirty的四个坐标就是想对于当前ViewGroup的坐标值了 dirty.offset([CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); //如果当前ViewGroup需要裁剪View //则将当前ViewGroup的区域与View的区域做求并集的操作 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; //如果当前ViewGroup需要裁剪View,且ViewGroup区域与View区域没有并集,则dirty置空 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //用于循环到下一个ViewGroup时做offset操作 location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } return mParent; } else {//如果当前ViewGroup中有动画要执行 mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = mLeft; location[CHILD_TOP_INDEX] = mTop; //如果需要对子View裁剪则设置dirty为当前ViewGroup区域 //如果不需要则求当前ViewGroup区域与原ditry区域并集 if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { dirty.set(0, 0, mRight - mLeft, mBottom - mTop); } else { // in case the dirty rect extends outside the bounds of this container dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; } return mParent; } } return null; }
invalidateChildInParent()主要是完成了dirty区域在调用该方法的ViewGroup中的更新,dirty指示的区域就是需要重绘制的区域。如果ViewGroup没有动画在执行,则dirty区域还是原来的区域,只需要通过偏移操作更改该区域的坐标值从相对于上一个ViewGroup(父ViewGroup),到相对于当前ViewGroup;如果有动画要执行,则表示当前整个ViewGroup都需要重绘,更改dirty值为当前ViewGroup 区域。
do-while最后一次循环最后会调用到ViewRootImpl.invalidateChildInParent()方法,进入该代码。
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); //如果传入一个null drity,则表示要重绘当前ViewRootImpl指示的整个区域 //如果传入一个empty dirty,则表示经过计算需要重绘的区域不需要绘制 if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } ... invalidateRectOnScreen(dirty); return null; }
调用到了ViewRootImpl.invalidateRectOnScreen()方法,进入该代码。
private void invalidateRectOnScreen(Rect dirty) { //mDirty记录的是当前ViewRootImpl里还未进行重绘需要重绘的区域 //mDirty会在ViewRootImpl.draw()方法结尾处设置为empty final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one //当前已有的dirty区域与此次dirty区域做并集 localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; //处理窗口缩放与做完并集的localDirty做交集 final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); //如果没有交集 if (!intersected) { localDirty.setEmpty(); } //mWillDrawSoon在performTraversals()方法开始时置为true,结束时置false //如果没有在执行performTraversals &&(intersected || 正在执行动画) if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } }
最后会调用到scheduleTraversals()方法,后续在请求到Vsync信号后,便会调用到peformTraversals()方法。