主目錄見:Android高級進(jìn)階知識(這是總目錄索引)
?前面一周由于休假所以沒有寫文章,今天剛回來呢弯院,打算給大家一起說說這兩個(gè)布局的性能對比,通過這個(gè)性能對比我們也能更好地使用這兩個(gè)布局仪糖,希望大家一起享受這一段旅程鹤竭。
同時(shí)推薦一篇ConstraintLayout 完全解析 快來優(yōu)化你的布局吧挂脑,這個(gè)是新的layout藕漱,大家可以看看。
一.目標(biāo)
今天主要是通過源碼來分析下這兩個(gè)布局的性能最域,但是不會非常詳細(xì)地一句一句代碼進(jìn)行解析谴分,不然代碼涉及的東西還蠻多,如果需要每個(gè)都懂得話可以留言我會說明镀脂。
1.弄懂LinearLayout和RelativeLayout的性能牺蹄;
2.明白在什么場景使用什么布局。
二.性能對比
?我們這里先說下我個(gè)人看法薄翅,在簡單布局可以用單層LinearLayout完成的布局我們可以選擇LinearLayout進(jìn)行布局沙兰,如果用單層LinearLayout完成不了而要嵌套的話氓奈,那么我們可以考慮用RelativeLayout來完成布局。
1.繪制流程
?通過前面View和ViewGroup的繪制原理源碼分析這篇文章我們知道了我們的繪制過程是從performTraversals()分別調(diào)用perfromMeasure鼎天、performLayout和performDraw這三個(gè)方法舀奶。這三個(gè)方法分別完成頂級View的measure、layout和draw三大流程斋射。然后遍歷子節(jié)點(diǎn)分別重復(fù)這幾個(gè)步驟育勺,直到整個(gè)view樹完成,view也就顯示出來罗岖。所以我們看下這幾個(gè)流程的耗時(shí)我們就知道他們的性能情況了涧至。
?首先我們選擇了一個(gè)一樣的布局,然后分別用RelativeLayout布局和LinearLayout布局桑包,我們看下布局的樣子:
然后我們看下頂層分別用LinearLayout和用RelativeLayout的耗時(shí)情況:
上面兩張圖是用Hierarchy Viewer里面看的南蓬,如果有興趣也可以自己去看看。我們看到這里的layout和draw這兩個(gè)流程時(shí)間差不多哑了,當(dāng)然由于這個(gè)工具有可能多次刷新會出現(xiàn)結(jié)果不同赘方,但是不同的是measure這個(gè)流程RelativeLayout用時(shí)都會相對長些,因?yàn)檫@里布局簡單不涉及多層嵌套弱左,所以RelativeLayout不能發(fā)揮出優(yōu)勢窄陡,我們來看看源碼是為什么?
2.LinearLayout
我們今天就來看看measure這個(gè)流程這兩個(gè)布局分別干了什么:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我們看到代碼很簡單科贬,就是根據(jù)orientation是垂直的還是水平的進(jìn)行布局泳梆。我們就來看看 measureHorizontal的源碼:
// See how wide everyone is. Also remember max height.
//獲得子view的寬度鳖悠,并記下最大的高度
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);//默認(rèn)返回0
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);//默認(rèn)返回0
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//獲取所有子視圖的lp榜掌,然后獲取總共的權(quán)重weight
totalWeight += lp.weight;
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
//如果LinearLayout寬度是已經(jīng)確定的。并且這個(gè)子view的width=0乘综,weight>0憎账,
//則mTotalLength只需要加上margin即可,
//由于是weight>0卡辰;該view的具體高度等會還要計(jì)算
if (isExactly) {
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
// Baseline alignment requires to measure widgets to obtain the
// baseline offset (in particular for TextViews). The following
// defeats the optimization mentioned above. Allow the child to
// use as much space as it wants because we can shrink things
// later (and re-measure).
if (baselineAligned) {
final int freeWidthSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED);
final int freeHeightSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED);
child.measure(freeWidthSpec, freeHeightSpec);
} else {
skippedMeasure = true;
}
} else {
//否則如果模式是wrap_cotent的話胞皱,那么就要先測量子view,然后將子view的寬高和間隔統(tǒng)計(jì)相加用
//mTotalLength 存儲起來
if (useExcessSpace) {
// The widthMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal width. We'll restore the original width of 0
// after measurement.
lp.width = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
final int childWidth = child.getMeasuredWidth();
if (useExcessSpace) {
// Restore the original width and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.width = 0;
usedExcessSpace += childWidth;
}
if (isExactly) {
mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
+ getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin
+ lp.rightMargin + getNextLocationOffset(child));
}
if (useLargestChild) {
largestChildWidth = Math.max(childWidth, largestChildWidth);
}
}
.........
}
我們看到LiearLayout的onMeasure中九妈,使用了mTotalLength來保存測量過的子視圖的總寬度反砌。在for循環(huán)中,如果是wrap_content的話萌朱,那么我們會調(diào)用measureChildBeforeLayout()方法宴树,其中一個(gè)參數(shù)是widthMeasureSpec,另外一個(gè)是usedWidth(已經(jīng)被子視圖使用的寬度)晶疼。每次for循環(huán)對child測量完畢后酒贬,程序就會調(diào)用getMeasuredWidth()方法來得到child的寬度又憨,然后添加進(jìn)mTotalLength 中來。這里面暫時(shí)沒有考慮weight>0的情況锭吨,因?yàn)槿绻紤]這個(gè)的話蠢莺,后面會進(jìn)行第二次的測量,父視圖會把剩余的寬度按照weight值的大小平均分配給相應(yīng)的子視圖零如。那么我們來看weight>0的情況躏将,這里的代碼也比較長,我們這里說明一下代碼邏輯:
1.weight>0,且width=0考蕾,mode=EXACTLY耸携,那么寬度就是share = (int) (childWeight * remainingExcess / remainingWeightSum),是根據(jù)剩余空間跟view的weight計(jì)算得到辕翰,也就是說如果剩余空間為零夺衍,那么視圖的大小也會為零。
2.weight>0,mode != EXACTLY,那么得到的寬度就是本身的控件寬度加上share 的寬度喜命。也就是說是wrap_content的話那么寬度是自身的寬度加上剩余的空間占比(也就是說能優(yōu)先獲得自身的布局寬度沟沙,然后再去加上剩余的空間占比)。
所以我們有結(jié)論得出壁榕,如果我們布局中設(shè)置了weight的話矛紫,那么LinearLayout的話會測量兩次,這樣明顯影響了性能牌里,所以我們應(yīng)該能不適用weight的時(shí)候就少用颊咬。
3.RelativeLayout
RelativeLayout的源代碼還是比較復(fù)雜的,而且里面的依賴關(guān)系是用圖來做的牡辽,而且里面會進(jìn)行圖的拓?fù)渑判蛟N覀冞@里同樣就不進(jìn)行一句一句地講解,我們先來手下RelativeLayout的測量做了哪些工作:
- 1.子視圖根據(jù)橫向關(guān)系和縱向關(guān)系排序 sortChildren();
- 2.初始化一些變量值态辛;
- 3.遍歷水平關(guān)系的View麸澜,將相對布局的關(guān)系轉(zhuǎn)化為左右坐標(biāo),然后確立水平方向的子View位置奏黑;
- 4.遍歷垂直關(guān)系的View炊邦,將相對布局關(guān)系轉(zhuǎn)化為垂直坐標(biāo),然后確立垂直方向的子View的位置熟史;
- 5.baseline計(jì)算馁害;
- 6.寬度和高度修正。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren();
}
//省略初始化變量代碼
.........
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}
if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}
if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
//省略寬度和高度修正代碼和baseline計(jì)算代碼
........
setMeasuredDimension(width, height);
}
從源碼里面我們可以看出來蹂匹,這邊兩次for循環(huán)分別會根據(jù)我們設(shè)置的依賴關(guān)系碘菜,比如A垂直依賴B,B水平依賴C,那么程序會進(jìn)行水平方向的依賴關(guān)系解析炉媒,然后確定坐標(biāo)和位置踪区。同樣垂直方向也是如此。這里的依賴關(guān)系節(jié)點(diǎn)是用圖的形式存儲吊骤,這里的代碼跟Behavior里面的源碼有點(diǎn)像缎岗,那個(gè)依賴關(guān)系也是用圖來存儲的,然后搜索的時(shí)候可以深度和廣度搜索排序白粉。有興趣大家可以了解一下數(shù)據(jù)結(jié)構(gòu)中圖的相關(guān)知識传泊。所以我們看到我們的RelativeLayout會進(jìn)行兩次的測量,這樣有可能會成為性能消耗的原因鸭巴,但是同時(shí)RelativeLayout在復(fù)雜布局時(shí)候有可能減少嵌套層數(shù)眷细。所以在復(fù)雜嵌套時(shí)候我們可以考慮使用他或者LinearLayout使用到weight的情況我們也可以考慮使用它。
總結(jié):我們上面的解釋也說的很清楚了鹃祖,如果有什么疑問或者錯(cuò)誤大家可以留言哈溪椎,同時(shí)附上大家一直說的一個(gè)問題:為什么Google給開發(fā)者默認(rèn)新建了個(gè)RelativeLayout,而自己卻在DecorView中用了個(gè)LinearLayout恬口?因?yàn)镈ecorView的層級深度已知且固定的校读,上面一個(gè)標(biāo)題欄,下面一個(gè)內(nèi)容欄祖能,采用RelativeLayout并不會降低層級深度歉秫,因此這種情況下使用LinearLayout效率更高。