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.
     * <p>
     * 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()方法的源码分析

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值