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():

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

scheduleTraversals() 方法裏面發送了一個消息,將執行TraversalRunnable 任務方法。
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()方法的源碼流程分析完畢;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章