View的invalidate()方法的源码分析
首先要明白invalidate()方法是做什么的?
View#invalidate():
/** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in * the future. * * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */
public void invalidate() { invalidate(true); }
英文注释的大致意思是:如果View是可见的,使整个View视图无效,然后在未来的某个时间点View的onDraw(android.graphics.Canvas)方法将被调用。
注意: 该方法必须在UI线程,也就是主线程中才能调用。如果要在非UI线程中调用,可以调用View#postInvalidate() 方法,这也是invalidate()和postInvalidate()的区别之一;
也就是说,invalidate()方法是用来刷新重绘当前的View的,如果当前View的布局尺寸、位置没有变化,仅仅是绘制内容变化了,那么我们就可以调用invalidate()方法。
跟踪源码进入 View#invalidate(boolean invalidateCache):
@UnsupportedAppUsage public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }// View#invalidate(boolean invalidateCache)方法内部,又调用了下面的方法 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { // 这里挑重点代码查看,以免迷失在代码的海洋中,与源码流程分析无关的代码,暂时不关注 ......省略代码 // 这里判断该子View是否可见或者是否处于动画中,如果子View不可见或者没有处于动画中,则不让该子View失效,即该子View不会被重绘 if (skipInvalidate()) { return; } ......省略代码 // 根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘 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)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; }// 设置PFLAG_DIRTY标记位 mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Propagate the damage rectangle to the parent view. 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); // 子View的ViewParent就是它的父View即ViewGroup // 调用了父类ViewParent的invalidateChild()方法 p.invalidateChild(this, damage); } // Damage the entire projection receiver, if necessary. ......省略代码 } }
该方法中,首先判断该子View是否可见或者是否处于动画中,如果子View不可见或者没有处于动画中,则不让该子View失效,即该子View不会被重绘。然后根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘。最后调用了父类ViewParent的invalidateChild()方法,子View的ViewParent就是它的父View即ViewGroup。
跟进ViewGroup#invalidateChild(View child, final Rect dirty) 方法:
public final void invalidateChild(View child, final Rect dirty) {......省略代码// parent为当前的ViewGroup ViewParent parent = this; if (attachInfo != null) { final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0; Matrix childMatrix = child.getMatrix(); final boolean isOpaque = child.isOpaque() && !drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); 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; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; ......省略代码 do { View view = null; if (parent instanceof View) { view = (View) parent; } 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 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; } } // 调用parent的invalidateChildInParent,即调用ViewGroup的invalidateChildInParent 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); }}
ViewGroup#invalidateChild(View child, final Rect dirty) 方法内部,不断的do while循环,直到循环到最外层view的invalidateChildInParent方法。
内层的parent是调用的ViewGroup的invalidateChildInParent方法。
最外层的View,即DecorView,也就是调用DecorView的ViewParent#invalidateChildInParent方法;
那么,DecorView的ViewParent是什么呢?上面的图,给出了答案,是ViewRootImpl。下面我们从源码中探寻究竟为何是ViewRootImpl:
在Activity中,当调用 setContentView() 方法后,经过installDecor() -> generateLayout(mDecor) -> mLayoutInflater.inflate(layoutResID, mContentParent)等方法调用后,Activity的布局文件就已成功添加到了DecorView的mContentParent中,此时,DecorView还未添加到Window中。
Activity调用onResume方法,然后调用makeVisible方法后,DecorView才被添加到Window中。
这里简要分析一下Activity的启动过程,不做过多分析,简单写一下,顺序调用ActivityThread的handleLauncheActivity,handleResumeActivity方法,handleResumeActivity方法中首先调用performResumeActivity方法,performResumeActivity方法中调用Activity中的performResume方法,之后调用Activity的onResume方法,最后调用Activity的makeVisible方法,makeVisible方法中会把当前的顶层DecoView通过WindowManager的addView方法添加到WindowManager中,而WindowManager的实现类WindowManagerImpl中调用的是WindowManagerGlobal的addView方法。
如下: 看注释的关系代码,这里实例化了一个ViewRootImpl,调用了setView方法,传入的view参数就是DecorView;
WindowManagerGlobal#addView:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {......省略代码 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. ......省略代码 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { // view即为addView传入的DecorView root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
ViewRootImpl#setView(View view, WindowManager.LayoutParams attrs, View panelParentView):
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view;......省略代码 // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout();// 设置DecorView的viewParent为ViewRootImpl view.assignParent(this); mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0; if (mAccessibilityManager.isEnabled()) { mAccessibilityInteractionConnectionManager.ensureConnection(); } if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } // Set up the input pipeline. ......省略代码 } }
ViewRootImpl#setView()方法,其中的参数view即为DecorView,在该方法里面调用了view.assigenParent(this),把ViewRootImpl设置为DecorView的ViewParent。
插入了一段小插曲,下面回头跟踪查看
ViewGroup#invalidateChildInParent(final int[] location, final Rect dirty):
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) { // either DRAWN, or DRAWING_CACHE_VALID if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) { // 调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { // 调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域 dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; } else { 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); } location[CHILD_LEFT_INDEX] = mLeft; location[CHILD_TOP_INDEX] = mTop; mPrivateFlags &= ~PFLAG_DRAWN; } mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; if (mLayerType != LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; }// 返回当前View的父容器,以便进行下一次循环 return mParent; } return null; }
调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标。接着调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环。
由于ViewGroup#invalidateChild() 方法里面的do while循环完最终会调用最外层 ViewRootImpl 里面的 invaludateChild 方法。
ViewRootImpl#invalidateChild(View child, Rect dirty):
@Override public void invalidateChild(View child, Rect dirty) { // 内部调用了invalidateChildInParent方法 invalidateChildInParent(null, dirty); }
ViewRootImpl#invalidateChildInParent(int[] location, Rect dirty):
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } }// 最后调用 invalidateRectOnScreen 方法 invalidateRectOnScreen(dirty); return null; }
该方法主要还是dirty区域的计算。然后调用
ViewRootImpl#invalidateRectOnScreen(Rect dirty):
private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; // Add the new dirty rect to the current one 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; final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty(); } if (!mWillDrawSoon && (intersected || mIsAnimating)) { // 重点走到这里 scheduleTraversals(); } }
该方法主要是刷新屏幕上面的一个Rect区域,而Rect区域就是调用invalidate方法的那个View大小。然后调用了ViewRootImpl的scheduleTraversals() 方法。
ViewRootImpl#scheduleTraversals():
@UnsupportedAppUsage void scheduleTraversals() { // 注意这个标志位,多次调用 requestLayout,只有当这个标志位为 false 时才有效 if (!mTraversalScheduled) { mTraversalScheduled = true; // 通过postSyncBarrier()设置Handler消息的同步屏障 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调 // 有了同步屏障mTraversalRunnable就会被优先执行 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调,有了同步屏障mTraversalRunnable就会被优先执行,至于为何有了同步屏障mTraversalRunnable就会被优先执行?可以查看分析Handler之同步屏障机制与Android的屏幕刷新机制在源码中的应用
ViewRootImpl#TraversalRunnable:
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
TraversalRunnable 任务方法中又调用了doTraversal() 方法。
ViewRootImpl#doTraversal() :
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); }// 走到这里,这里也是很多博客开始分析View的绘制流程时,选择的切入入口 performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
performTraversals() 这个方法非常重要,方法非常多,简单讲我们需要关注以下几个方法:
ViewRootImpl#performTraversals() :
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; mIsInTraversal = true; ......省略代码 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 // 关注方法 1 performMeasure 测量方法 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 (measureAgain) { // 关注方法 1 performMeasure 测量方法 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; } } } else { maybeHandleWindowMove(frame); } final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { // 关注方法 2 performLayout 位置摆放方法 performLayout(lp, mWidth, mHeight); ......省略代码 } ......省略代码 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(); } // 关注方法 3 performDraw 绘制方法 performDraw(); } else { ......省略代码 } mIsInTraversal = false; }
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 { // 调用View的measure方法,measure方法内部又会调用View的onMeasure方法对View进行测量 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
该方法内部调用了View的measure方法,measure方法内部又会调用View的onMeasure方法对View进行测量。
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.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { ......省略代码 if (validLayoutRequesters != null) { ......省略代码 measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ......省略代码 } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
该方法内部调用了View的layout方法,layout方法内部又会调用View的onLayout方法对View进行布局摆放。
ViewRootImpl#performDraw() :
private void performDraw() {......省略代码 try { boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; } } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
该方法中调用了ViewRootImpl#draw()方法,ViewRootImpl#draw()方法又调用了ViewRootImpl#drawSoftware()方法,ViewRootImpl#drawSoftware()方法中,当前的View调用了View#draw() 方法,即mView.draw(canvas)对当前的View进行绘制;
ViewRootImpl#performTraversals() 方法依次可能会调用了performMeasure,performLayout,performDraw。这三个和我们常用的onMeasure,onLayout,onDraw方法就很像,因为这三个方法最终也会调用我们常用的onXXX方法。但在这里这三个方法不一定都会调用,当我们调用invalidate的时候,也就是说我们只想调用绘制我们的View的方法,这个时候只会调用到performDraw方法;当我们的view如果位置发生改变了,则也会调用到performLayout方法;如果大小也改变了,则也会调用perforMeasure方法。这三个方法就会回调View里面的mesure,layout,draw方法,measure内部会回调onMeasure,layout内部会回调onLayout,draw内部会回调onDraw。
至此,View#invalidate() 方法的源码流程分析完毕;
如果对 View#requestLayout() 方法感兴趣的,可以查看下一篇View的requestLayout()方法的源码分析
更多相关文章
- Android(安卓)调用其他应用-QQ音乐
- android > 手机MIC,听筒音量监听
- Android(安卓)WebView 图片自适应屏幕宽度
- Android学习札记36:一个关于onSaveInstanceState ()方法的例子
- android最近任务列表,删除某个应用操作
- android仿苹果Iphone桌面源码
- android动态布局方法总结
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用