Android 自定義控件 requestLayout / invalidate

Read The Fucking Source Code

引言

Android自定義控件涉及View的繪制分發(fā)流程

源碼版本(Android Q — API 29)

本文涉及Android繪制流程

Android 繪制流程

1. requestLayout

1.1 View的 requestLayout 過(guò)程

1.2 requestLayout自底向上

1.2.1 我們來(lái)看View中的requestLayout()方法。
public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        //View樹(shù)正在進(jìn)行布局流程
        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()) {
                //ViewRootImpl是否會(huì)截獲處理布局(已經(jīng)在不居過(guò)程中),后面會(huì)有說(shuō)明
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        //View設(shè)置標(biāo)記位,需要重新布局
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        //向父容器請(qǐng)求布局,即調(diào)用父容器的requestLayout方法,為父容器添加PFLAG_FORCE_LAYOUT標(biāo)記位
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
1.2.2 我們來(lái)看ViewRootImpl中的requestLayoutDuringLayout()方法。
//返回值決定請(qǐng)求布局發(fā)起方是否進(jìn)行遞歸布局申請(qǐng),fase:中斷布局請(qǐng)求;ture:布局請(qǐng)求繼續(xù)執(zhí)行。
boolean requestLayoutDuringLayout(final View view) {
        if (view.mParent == null || view.mAttachInfo == null) {
            // Would not normally trigger another layout, so just let it pass through as usual
            return true;
        }
        //正在布局的視圖包含請(qǐng)求view,則添加
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        //是否允許在不居中申請(qǐng)布局請(qǐng)求
        if (!mHandlingLayoutInLayoutRequest) {
            // Let the request proceed normally; it will be processed in a second layout pass
            // if necessary
            return true;
        } else {
            // Don't let the request proceed during the second layout pass.
            // It will post to the next frame instead.
            return false;
        }
    }
1.2.3 ViewGroup中沒(méi)有復(fù)寫requestLayout方法,所以ViewGroup也是調(diào)用和View同樣的方法。
1.2.4 我們來(lái)看ViewRootImpl中的requestLayout()方法。
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //線程檢查
            checkThread();
            mLayoutRequested = true;
            //請(qǐng)求注冊(cè)Vsync信號(hào)觸發(fā)繪制
            scheduleTraversals();
        }
    }
1.2.5 requestLayout()小結(jié)

?requestLayout事件層層向上傳遞,直到DecorView(即根View),又會(huì)傳遞給ViewRootImpl(ViewRootImpl接管了DecorView的ViewParent功能),也即是說(shuō)子View的requestLayout事件,最終會(huì)被ViewRootImpl接收并得到處理。縱觀這個(gè)向上傳遞的流程,其實(shí)是采用了責(zé)任鏈模式,即不斷向上傳遞該事件,直到找到能處理該事件的上級(jí),作為View的外交大管家,只有ViewRootImpl能夠處理requestLayout事件。

2. invalidate

2.1 View的 invalidate 過(guò)程

2.2 invalidate自底向上

2.2.1 我們來(lái)看View中的invalidate()方法。
public void invalidate() {
        invalidate(true);
    }

public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
2.2.2 我們來(lái)看View中的invalidateInternal()方法。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        //代碼省略……

        //這里判斷該子View是否可見(jiàn)或者是否處于動(dòng)畫中
        if (skipInvalidate()) {
            return;
        }

        // Reset content capture caches
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mCachedContentCaptureSession = null;

        //根據(jù)View的標(biāo)記位來(lái)判斷該子View是否需要重繪,假如View沒(méi)有任何變化,那么就不需要重繪
        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;
            }

            //設(shè)置PFLAG_DIRTY標(biāo)記位(增加臟區(qū)標(biāo)記位)
            mPrivateFlags |= PFLAG_DIRTY;

            // 如果緩存失效,則清空對(duì)應(yīng)標(biāo)記
            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;
            //把需要重繪的區(qū)域傳遞給父容器
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

            //代碼省略……
        }
    }
2.2.3 我們來(lái)看ViewGroup中的invalidateChild()方法。
@Deprecated(請(qǐng)注意這個(gè)廢棄的標(biāo)識(shí),這個(gè)方法以后可能要廢棄)
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        //如果硬件加速開(kāi)啟(默認(rèn)硬件加速是開(kāi)啟的)
        //現(xiàn)在能理解為什么這個(gè)方法標(biāo)記未廢棄方法了吧,因?yàn)檫M(jìn)行了硬件加速的優(yōu)化,具體細(xì)節(jié)以后在講。
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            //進(jìn)一步處理,用的是新的代替廢棄方法的執(zhí)行方法。
            onDescendantInvalidated(child, child);
            return;
        }

        //代碼省略……

        //如果硬件加速關(guān)閉,才會(huì)之前版本的遞歸遍歷過(guò)程(此過(guò)程不講了,網(wǎng)上大部分的博客還停留在這個(gè)版本階段)
        parent = parent.invalidateChildInParent(location, dirty);

       //代碼省略……

    }
2.2.4 我們來(lái)看ViewGroup中的onDescendantInvalidated()方法。
//經(jīng)過(guò)針對(duì)硬件加速優(yōu)化后的代碼,就顯得清新脫俗了
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
        /*
         * HW-only, Rect-ignoring damage codepath
         *
         * We don't deal with rectangles here, since RenderThread native code computes damage for
         * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
         */

        // if set, combine the animation flag into the parent
        mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);

        if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
            // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
            // optimization in provides in a DisplayList world.
            mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

            // simplified invalidateChildInParent behavior: clear cache validity to be safe...
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // ... and mark inval if in software layer that needs to repaint (hw handled in native)
        if (mLayerType == LAYER_TYPE_SOFTWARE) {
            // Layered parents should be invalidated. Escalate to a full invalidate (and note that
            // we do this after consuming any relevant flags from the originating descendant)
            mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
            target = this;
        }

        //遞歸調(diào)用父類的onDescendantInvalidated,最終會(huì)調(diào)用到ViewRootImpl中的這個(gè)方法。
        if (mParent != null) {
            mParent.onDescendantInvalidated(this, target);
        }
    }
2.2.5 我們來(lái)看ViewRootImpl中的onDescendantInvalidated()方法。
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
        // TODO: Re-enable after camera is fixed or consider targetSdk checking this
        // checkThread();
        if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
            mIsAnimating = true;
        }
        //繪制方法
        invalidate();
    }
2.2.6 我們來(lái)看ViewRootImpl中的invalidate()方法。
void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            //繪制請(qǐng)求,注冊(cè)Vsync信號(hào),等待上報(bào)繪制刷新。
            scheduleTraversals();
        }
    }
2.2.7 invalidate小結(jié)

?當(dāng)子View調(diào)用了invalidate方法后,會(huì)為該View添加一個(gè)標(biāo)記位,同時(shí)不斷向父容器請(qǐng)求刷新,父容器通過(guò)計(jì)算得出自身需要重繪的區(qū)域,直到傳遞到ViewRootImpl中,最終觸發(fā)performTraversals方法,進(jìn)行開(kāi)始View樹(shù)重繪流程(只繪制需要重繪的視圖)。

2.2.8 postInvalidate簡(jiǎn)介

?postInvalidate和invalidate的作用是一樣的,唯一的區(qū)別是,postInvalidate可以在子線程中調(diào)用請(qǐng)求刷新UI。為什么呢?因?yàn)檎?qǐng)求重新布局和繪制,最終都會(huì)在ViewRootImpl中處理,而ViewRootImpl會(huì)在請(qǐng)求方法中,進(jìn)行線程檢查(是否是UI線程)。當(dāng)子view中有請(qǐng)求繪制的需求怎么辦,那么就用postInvalidate。其實(shí)postInvalidate的功能就是 線程切換 + invalidate調(diào)用。

3 問(wèn)題思考

requestLayout 和 invaldate 有什么區(qū)別?

  • requestLayout 和 invalidate 都會(huì)觸發(fā)整個(gè)繪制流程。但是在 measure 和 layout 過(guò)程中,只會(huì)對(duì) flag 設(shè)置為 FORCE_LAYOUT 的情況進(jìn)行重新測(cè)量和布局,而 draw 只會(huì)重繪 flag 為 dirty 的區(qū)域。
  • requestLayout 是用來(lái)設(shè)置 FORCE_LAYOUT 標(biāo)志,invalidate 用來(lái)設(shè)置 dirty 標(biāo)志。所以 requestLayout 只會(huì)觸發(fā) measure 和 layout,invalidate 只會(huì)觸發(fā) draw。
  • 所以一般都是組合使用。比如:只要刷新的時(shí)候就調(diào)用 invalidate,需要重新 measure 就調(diào)用 requestLayout,后面再跟個(gè) invalidate(為了保證重繪)

?

小編的擴(kuò)展鏈接

《Android 視圖模塊 全家桶》
?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容