Read The Fucking Source Code
引言
Android自定義控件涉及View的繪制分發(fā)流程
源碼版本(Android Q — API 29)
本文涉及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(為了保證重繪)
?