??經(jīng)過兩個多月的框架源碼轟炸,感覺自己的腦子變得有點懵逼了。在這兩個多月里面,先后看了RxJava、OkHttp和Retrofit的源碼,并且將自己的理解寫成了博客,作為記錄;后續(xù)又看了EventBus和ButterKnife的源碼,本來都想寫成博客的,但是覺得這兩個框架比較簡單,因此就沒有寫(純粹個人想法,大家有意見的話,盡管噴);最后,就是簡單的看了一下Glide的源碼,太特么的難了,看不懂看不懂,看到一半就放棄了,應(yīng)該是自己的功力不夠,自己再沉淀沉淀,之后再去試試吧。
??今天,我將帶來一篇比較輕松的文章--View的mesure、layout、draw三大流程。本文將詳細(xì)講解View的三大流程,閱讀本文最好有牢固的Android基礎(chǔ),并且對Android View的基本結(jié)構(gòu)有所了解。
??說到寫本文的經(jīng)歷還有點曲折,本來一開始打算好好的寫這篇文章,但是寫著寫著感覺沒什么寫的,然后自己轉(zhuǎn)而去看RecyclerView的源碼,將RecyclerView的三大流程簡單的梳理完畢之后,發(fā)現(xiàn)RecyclerView的三大流程跟普通的View有很大的不同,所以決定重新來寫這篇文章。說到底,本文就是為了后面的RecyclerView源碼打基礎(chǔ)??。
??好了,廢話少說,進(jìn)入正文。本文參考資料:
??1. Android View源碼解讀:淺談DecorView與ViewRootImpl
??2. Android View 測量流程(Measure)完全解析
??3. 從requestLayout()初探View的繪制原理
??4.Android View 繪制流程(Draw) 完全解析
??5. 任玉剛大神的《Android開發(fā)藝術(shù)探索》
??注意:本文所有源碼都基于 API 27。
1. 概述
??View的三大流程非常的重要,重要到那種程度呢?幾乎達(dá)到了面試必問的程度,同時,在實際的開發(fā)中,如果熟悉三大流程的話,自定義View可以寫的非常6,當(dāng)然在解決那種迷之問題時,熟悉三大流程必將事倍功半。
??View的三大流程,分別是measure、layout、draw三個過程。我想,不用解釋這三大流程分別是干嘛的吧?咱們從它的英文意思上就可以知道。
??在正式分析源碼之前,我們先來通過一張圖片對三大流程有一個整體的了解。

??上面的流程圖從大概上解釋了三大流程的過程,但是很多的細(xì)節(jié)都沒有解釋到,這就需要我們從源碼的程度來分析了。接下來,我們正式進(jìn)入View三大流程的源碼分析。
2. ViewRootImpl
??View的三大流程從ViewRootImpl的performTraversals方法開始的,具體是怎么調(diào)用的這個方法來的,這里就不詳細(xì)的解釋了,因為這里面涉及到Activity的創(chuàng)建、setContentView、PhoneWindow等等。這里我們只需要知道,performTraversals方法就是三大流程的開始。但是整個過程是怎么傳遞下去的呢?這個我們必須得對整個Activity的布局結(jié)構(gòu)有一個整體的認(rèn)識,我們來看看。
??由于這部分的知識不是本文的核心內(nèi)容,所以這里就不貼出源碼來展示了。我就簡單的解釋一下。
??每一個Activity都一個Window對象的,Activity所有的View操作都托管給這個Window,我們可以把這個Window對象看成Activity的代理對象,包括Activity的setContentView和findViewById方法都是由Window接管的。所以,我們看到Activity的布局,實際上是Window的布局。
??同時,我們還知道,Android中的View成樹形結(jié)構(gòu),樹必須就得有一個根,那么在Window中,這個View樹的根是什么呢?沒錯,就是我們DectorView。而DectorView本身是一個FrameLayout,并沒有什么優(yōu)勢?所以通常在DectorView里面還會有一個類似于LinearLayout,這個LinearLayout裝著兩部分的布局,一部分是ActionBar,另一部分是contentView,也就是我們通過setContentView方法設(shè)置的布局那部分,contentView的id固定是android.R.id.content,這個在開發(fā)中有一定的幫助。
??而ViewRootImpl的performTraversals方法就是DecorView的三大流程,然后借助DecorView將這三個流程傳遞下去,就像是事件分發(fā)機(jī)制一樣,一層一層傳遞下去。然后DecorView雖然是一個ViewGroup,但是它的三大流程跟普通的ViewGroup相比,有一定的差別。
??這里,我只是對Activity的布局基本介紹一下,具體的原理和底層的代碼我也不是很了解,所以也不好深入的分析這一塊,況且本文并不是分析這一塊的知識,所以,這里我就簡單的說明一下。現(xiàn)在我們來開始對源碼進(jìn)行分析,先來看看performTraversals方法相關(guān)代碼:
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);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
??performTraversals方法比較長,這里我只是將關(guān)鍵性代碼展示出來,在這里我們將知道三大流程的調(diào)用順序,最先是measure過程,通過performMeasure方法開始的;其次,layout過程通過performLayout方法開始;最后,draw過程通過performDraw方法開始的。接下來,我們簡單的看一下這三個方法。為什么簡單看一下呢?因為這三個方法就是操作分發(fā)到DecorView,過程是非常的簡單。
(1). performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
??performMeasure方法里面幾乎沒做什么,就是把Measure操作傳遞到DecorView里面,而這列mView就是DecorView對象。
(2). performLayout
??performLayout方法比較長,這里就不詳細(xì)的分析整個過程,但是最終的結(jié)果就是調(diào)用了DecorView的layout方法。待會我們在分析DecorView時,將會詳細(xì)的分析。
(3). performDraw
??performDraw方法跟performLayout方法一樣,最后調(diào)用DecorView的draw方法,來繪制View。具體的細(xì)節(jié),之后我們會詳細(xì)的分析。這里我們先有一個概念就行。
3. meaure
??三大流程相互獨立,如果合在一起分析難免會繞圈子,所以打算一一的來分析,將每個流程單獨的打通。首先我們來看看measure流程。
(1).measure方法
??measure流程從ViewRootImpl的performMeasure方法開始,調(diào)用了mView是什么呢?沒錯,就是DecorView。DecorView的measure方法時從View那里繼承過來的。同時,不僅僅是DecorView,所以控件的measure方法都是View那里繼承過來的,因為measure是一個final方法,不能重寫。接下來,我們來看看View的measure方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
}
??View的measure方法比較簡單,為了代碼簡潔,我省略了很多沒必要的代碼,我們只來看看核心代碼。整個measure方法流程,我們只需要記住一點,就是判斷調(diào)用onMeasure方法,其他的代碼都是來幫助達(dá)到這個目的的。
??我們來看看,什么時候需要調(diào)用onMeasure方法,什么時候又不需要調(diào)用onMeasure方法,而這種時候為什么不要調(diào)用onMeasure方法。這三個問題,是我們重點關(guān)心的。
??從代碼中看來,我們知道forceLayout為true或者needsLayout為true時,有可能會調(diào)用onMeasure方法。而這兩個方法有表示什么意思呢?
??forceLayout變量,我們從名字就知道是什么意思,判斷時候強(qiáng)制布局,這個非常理解?那這個變量在什么時候為true呢?從View的Api方法中,我們找到了一個方法--forceLayout方法。
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
}
??在forceLayout方法里面,這里mPrivateFlags變量跟 PFLAG_FORCE_LAYOUT做了一個或的位運算,所以在measure方法里面,forceLayout才會為true:
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
??不過這里需要注意的是,如果View第一次調(diào)用measure方法,forceLayout是肯定為true的。具體是為什么,我也不太清楚,但是我們可以通過下面的代碼來驗證一下:
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
try {
Field flags = this.getClass().getField("mPrivateFlags");
flags.setAccessible(true);
int anInt = flags.getInt(this);
Log.i("pby123", " " + ((anInt & (0x00001000)) == (0x00001000)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
??下面就是log日志:

??所以我們可以得出一個結(jié)論,一個
View的onMeasure方法至少會被執(zhí)行一次。??其實,我們可以這樣來想,如果在某些情況下
onMeasure方法不會被執(zhí)行,那么我們在外部調(diào)用View的getMeasureWidth方法始終得到的是0,這是不可能的。同時,如果getMeasureWidth方法返回值為0的話,那么在layout階段,我們根本不知道怎么進(jìn)行布局,這也是不可能的。這樣,我們就從側(cè)面可以得出,onMeasure方法至少會被執(zhí)行一次。??那為什么需要判斷是否執(zhí)行
onMeasure方法呢?這是為了避免多次執(zhí)行的onMeasure方法。??另一個變量就是
needsLayout,這個變量我們從名字上就可以判斷出來,表示是否需要布局,這個變量在什么時候為true呢?
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
??首先是判斷當(dāng)前的寬高是否老的寬高相同,如果相同,沒必要再次測量,同時如果當(dāng)前View的mode是EXACTLY和match_parent都沒必要測量。為什么在EXACTLY和match_parent時,不要調(diào)用onMeasure測量呢?
??首先當(dāng)mode為EXACTLY時,表示當(dāng)前View的寬高在第一次調(diào)用onMeasure方法已經(jīng)定死了,沒必要調(diào)用onMeasure方法進(jìn)行測量。
??其次就是match_parent,跟EXACTLY一樣,在父View分發(fā)measure事件下來時,也是經(jīng)過第一次measure方法之后,寬高已經(jīng)定死了,后續(xù)就沒必要再次測量。
??將measure方法簡單的分析一下之后,我們來看看DecorView的onMeasure方法,看看怎么測量自己和測量子View的。
(2).onMeasure方法
??DecorView的onMeasure方法比較長,我先簡單將這個方法分為過程,然后一一來分析。
1.根據(jù)mode,來計算
widthMeasureSpec和heightMeasureSpec
2.如果存在outset,并且mode不為UNSPECIFIED,那么就會考慮到outset,重新計算widthMeasureSpec和heightMeasureSpec
3.調(diào)用super.onMeasure方法,進(jìn)行真正的測量。
??前兩步都沒有什么可以分析,都是基本的操作,相信熟悉Android測量規(guī)則的同學(xué)對此不會陌生。我們的重點在第三步里面。由于DecorView繼承于FrameLayout,所以,我們來看看FrameLayout的onMeasure方法。
??FrameLayout的onMeasure方法也比較長,這里先分為幾個過程。
- 調(diào)用每個child的measure方法,測量每個child的寬高;并且記錄設(shè)置了
match_parent屬性的child- 調(diào)用
setMeasuredDimension方法,對自身寬高進(jìn)行設(shè)置。- 對設(shè)置了
match_parent屬性的child進(jìn)行測量。
??整個過程還是比較清晰,我們一個一個來分析。首先來看看第一個過程:
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
??這個過程,我們可以將它分成3個部分來看:
1 . 調(diào)用
measureChildWithMargins方法對子View的進(jìn)行測量。
- 不斷更新
maxHeight和maxWidth的值,主要是用于父View的測量,如果父View本身為wrap_content,這兩個值就非常的重要。- 記錄下設(shè)置
match_parent屬性的child,當(dāng)父View的寬高確定之后,在進(jìn)行第二次測量。
??2和3我們都不用看了,重點來看看measureChildWithMargins方法。還記得在很久很久以前,我就分析過這個方法,有興趣的同學(xué)可以去看看:Android 踩坑系列-ViewGroup的子View真正實現(xiàn)Margin屬性。好了,我們來看看measureChildWithMargins方法:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
??measureChildWithMargins方法比較簡單,就是通過調(diào)用getChildMeasureSpec方法來獲取child的MeasureSpec,然后將計算完畢的MeasureSpec傳遞到child的measure進(jìn)行真正測量。這里的重點在getChildMeasureSpec方法,也是整個Android系統(tǒng)中的View測量核心之一,從這個方法里面,我們可以獲得很多的測量規(guī)則。我們重點分析分析:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
??在分析這個方法之前,我們先對每個變量有一個認(rèn)識。
| 變量名 | 類型 | 含義 |
|---|---|---|
| spec | int | 父View的MeasureSpec,在getChildMeasureSpec方法里面,主要是通過這個變量來獲得父View的測量mode。因為子View的MeasureSpec是由父View的MeasureSpec和子View的MeasureSpec共同決定的 |
| padding | int | 主要是記錄父View的padding和子View的margin
|
| childDimension | int | 子View的MeasureSpec,與spec共同決定子View的MeasureSpec
|
??整個getChildMeasureSpec方法比較簡單,分為三種大情況,每種大情況又分為三種小情況,所以一共9種情況?,F(xiàn)在我們通過一張表來分析。

??上面表中就詳細(xì)的分析了每種情況下規(guī)則,這里我就不多說了。
??通過
getChildMeasureSpec方法,我們可以獲得child的MeasureSpec,然后調(diào)用child的measure方法進(jìn)行測量,這就將measure事件分發(fā)下去了??對第一個過程分析完畢之后,我們來看第二個過程:調(diào)用
setMeasuredDimension方法,對自身寬高進(jìn)行設(shè)置。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
??這一步比較簡單,通過resolveSizeAndState方法來獲得父View的MeasureSpec。這里主要是考慮到父View可能是warp_content,所以有maxHeight和maxWidth參與,這里就不分析resolveSizeAndState方法了,有興趣的同學(xué)可以看看。
??最后就是測量設(shè)置了match_parent的child,這個過程跟第一個過程比較像,這里就在就不分析了。
??整個measure過程,我們算是分析完畢了。這里我做一個簡單的總結(jié)。
- measure過程從
DecorView的measure方法開始,而measure本身不會進(jìn)行測量,而是分發(fā)到了onMeasure方法。由于DecorView繼承于
FrameLayout,所以調(diào)用的是FrameLayout的onMeasure方法。FrameLayout的onMeasure方法會測量自身,同時同時會將測量事件分發(fā)到每個View手里,從而完成了整個View樹的測量。
??分析完畢measure過程,現(xiàn)在我們來看看layout過程。
4. layout
??前面已經(jīng)說了,ViewRootImpl會通過performLayout方法來分發(fā),而performLayout方法最終會調(diào)用DecorView的layout方法進(jìn)行布局。
??我們先來看看performLayout方法:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
final View host = mView;
if (host == null) {
return;
}
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
}
}
??整個performLayout方法比較長,我將它分為兩個部分。
- 如果
host不為null,也就是DecorView不為null,調(diào)用DecorView的layout方法,將布局操作分發(fā)下去。- 如果
mLayoutRequesters不為空的話,進(jìn)行第二次布局。至于mLayoutRequesters什么不為空,這就涉及到requestLayout方法了,后續(xù)我會單獨寫一篇文章來分析這個方法,本文不做過多的講解。
??這里,我們重點的看第一個部分。第一個部分調(diào)用了DecorView的layout方法。而DecorView的layout方法最終會調(diào)用到View的layout方法,我們直接來看View的layout方法:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
??View的layout方法也比較簡單,我將它分為兩個部分:
- 調(diào)用onLayout方法,進(jìn)行真正的布局操作。
- 回調(diào)
OnLayoutChangeListener的onLayoutChange方法,告訴觀察者當(dāng)前的布局已經(jīng)改變了。
??第二部分沒有分析的必要,這個相信大多數(shù)的同學(xué)已經(jīng)司空見慣了。我們重點來看看onLayout方法,而View的onLayout方法本身是一個空方法。從這個空方法,我們可以得出兩點結(jié)論:
- 普通的View調(diào)用layout方法進(jìn)行布局,其實就是簡單將left、top、right、bottom4個變量記錄下來,并沒有做其他的操作布局。
ViewGroup必須實現(xiàn)onLayout方法,制定子View的布局規(guī)則。這就是ViewGroup有一個抽象方法的原因。
??既然View的layout調(diào)用了onLayout方法,接下來我們來看看DecorView的onLayout方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
if (mApplyFloatingVerticalInsets) {
offsetTopAndBottom(mFloatingInsets.top);
}
if (mApplyFloatingHorizontalInsets) {
offsetLeftAndRight(mFloatingInsets.left);
}
// If the application changed its SystemUI metrics, we might also have to adapt
// our shadow elevation.
updateElevation();
mAllowUpdateElevation = true;
if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
??DecorView的onLayout方法,我也簡單將它分為兩步:
- 調(diào)用
super.onLayout方法,也就是FrameLayout的onLayout方法來進(jìn)行布局。- 根據(jù)
mOutsets來調(diào)整位置。至于mOutsets是什么,抱歉,我也不知道??。
??看來我們看看FrameLayout的onLayout方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
??好嘛,又調(diào)用layoutChildren方法。在layoutChildren方法里面才是真正對child進(jìn)行布局的操作。
??這里就不對layoutChildren方法進(jìn)行展開了,因為比較簡單。就是根據(jù)每種ViewGroup不同的布局特性,進(jìn)行計算每個view的left、top、right和bottom,然后調(diào)用child的layout方法。
??如果child是一個普通的View的話,那么調(diào)用layout方法就是記錄下4個值,等待draw流程的到來;如果child是一個ViewGroup的話,就會像FrameLayout一樣,將layout事件分發(fā)下去。
??如上,就是整個View的layout流程,這里我做一個簡單的總結(jié)。
- layout過程從
DecorView的layout方法(也是View的layout方法)開始。在View的layout方法里面,會記錄下自身的left、top、right、bottom4個屬性,等待繪制,同時會調(diào)用onLayout方法將layout事件分發(fā)下去。- 如果是普通的
View,在layout方法里面調(diào)用onLayout方法是沒有用的,因為在View里面,onLayout方法是一個空方法;如果是一個ViewGroup,在onLayout里面,會調(diào)用每個child的layout方法。這樣整個layout流程就走通了。
??分析完layout流程之后,我們再來看看三大流程的最后一個流程--draw。
5. draw
??前面已經(jīng)說了,View樹的draw操作是從ViewRootImpl的performDraw方法開始的?,F(xiàn)在我們來看看performDraw方法。
private void performDraw() {
// ······
try {
draw(fullRedrawNeeded);
} finally {
}
// ······
}
??performDraw方法比較長,這里我將代碼簡化了一下。說到底,performDraw方法就是調(diào)用draw方法。
??我們來看一下draw方法,整個draw方法比較長,我簡單的將它分為幾個部分:
- 根據(jù)
fullRedrawNeeded變量,來計算dirty。dirty是一個矩陣,表示這次繪制的范圍。- 調(diào)用
drawSoftware方法進(jìn)行繪制。
??整個draw方法比較復(fù)雜,因為這里面涉及到動畫之類的。如果此時在動畫,表示本次繪制并不是最終的繪制,所以需要調(diào)用scheduleTraversals方法往主線程post一個Message用來下次繪制。
??其次,dirty的計算也是比較復(fù)雜的,我們這里也不去分析,因為這些都是計算,如果深入分析的話,容易將我們聰明的大腦搞暈??。
??我們還是直接來看drawSoftware方法吧。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
return false;
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
mView.draw(canvas);
} finally {
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
??整個drawSoftware方法比較長,我簡化了一下代碼。這里,我先將整個方法分為3個部分:
- 根據(jù)
dirty矩陣獲得繪制的Canvas對象- 調(diào)用
DecorView的draw方法,繪制整個View樹- 釋放
Canvas
??我們一一的分析,首先來看看第一步。
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
??這里通過mSurface來鎖定一塊畫布,從而保證后續(xù)的繪制操作是線程安全的。
??與之對應(yīng)的是,最后是釋放了這塊區(qū)域。
??我們重點的是是如下的代碼:
mView.draw(canvas);
??上面的代碼最終是調(diào)用View的draw方法。我們來看看View的draw方法:
public void draw(Canvas canvas) {
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
return;
}
//······
}
??整個draw方法的流程非常的清晰,一個分為7步:
- 調(diào)用
drawBackground方法,繪制背景。- 保存當(dāng)前View的畫布層次,這一步只在繪制fading edge才會執(zhí)行。
- 調(diào)用
onDraw方法,繪制View自身。- 調(diào)用
dispatchDraw方法,繪制children- 繪制fading edge,這個只在View本身需要繪制ading edge才會執(zhí)行。
- 調(diào)用
onDrawForeground方法,繪制View的前景。- 調(diào)用
drawDefaultFocusHighlight方法,繪制高亮部分。
??View通過這7步就將整個View樹繪制完畢。這里,我們就不對每個過程做詳細(xì)的分析,因為每個過程都可以寫的非常多,況且,我也不知道??。
??說到draw流程,就會想到invalidate和postInvalidate這兩個吊的一逼的方法,后續(xù)我會專門寫文章來分析這兩個方法,這里就不糾結(jié)了。
??draw流程算是分析完畢了,這里我對整個draw做一個小小的總結(jié)。
- draw流程是從
ViewRootImpl的performDraw方法開始,在這個方法主要是調(diào)用draw方法來進(jìn)行操作。ViewRootImpl的draw方法主要是做了兩步,一是計算畫布區(qū)域,用于后面獲取畫布對象;二是調(diào)用drawSoftware方法來進(jìn)行操作。drawSoftware方法主要做了3步,一是獲得鎖定一個畫布對象;二是調(diào)用View的draw啟動整個draw流程的執(zhí)行;三是釋放畫布對象。View的draw方法一共分為7步。每步做了可以參考上面的說明,這里就不重復(fù)的介紹了。對于View的draw方法,我們沒必要去沒比較去糾結(jié)每步是怎么做的,因為這樣容易導(dǎo)致深入源碼,不可自拔。
6. 總結(jié)
??View三大流程的流程到這里算是已經(jīng)結(jié)束,總的來說,介紹比較粗糙。但是我們分析源碼,沒必要去糾結(jié)每一行代碼,搞懂整個流程就OK,因為整個Android framework架構(gòu)是非常的復(fù)雜。
??這里我對三大流程做一個簡單的總結(jié)。
- 三大流程從View都是從
ViewRootImpl的performTraversals方法,分別調(diào)用performMeasure、performLayout和performDraw方法進(jìn)行三大流程的分發(fā)。- 三大流程的執(zhí)行流程非常的相似,都是一種View樹的遞歸遍歷思想。
??三大流程的源碼分析到此就結(jié)束了,接下來我會趁熱打鐵,進(jìn)一步的分析requestLayout、invalidate和postInvalidate這三個方法。因為這三個方法跟layout和draw兩個流程有關(guān)。