性能優(yōu)化(1.4)-各種布局的性能對比(LinearLayout,RelativeLayout)

主目錄見: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í)情況:


RelativeLayout
LinearLayout

上面兩張圖是用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效率更高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末养铸,一起剝皮案震驚了整個(gè)濱河市雁芙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钞螟,老刑警劉巖兔甘,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筛圆,居然都是意外死亡裂明,警方通過查閱死者的電腦和手機(jī)椿浓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門太援,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扳碍,你說我怎么就攤上這事提岔。” “怎么了笋敞?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵碱蒙,是天一觀的道長。 經(jīng)常有香客問我,道長赛惩,這世上最難降的妖魔是什么哀墓? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮喷兼,結(jié)果婚禮上篮绰,老公的妹妹穿的比我還像新娘。我一直安慰自己季惯,他們只是感情好吠各,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勉抓,像睡著了一般贾漏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藕筋,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天纵散,我揣著相機(jī)與錄音,去河邊找鬼隐圾。 笑死困食,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翎承。 我是一名探鬼主播硕盹,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叨咖!你這毒婦竟也來了瘩例?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤甸各,失蹤者是張志新(化名)和其女友劉穎垛贤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趣倾,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡聘惦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儒恋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片善绎。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诫尽,靈堂內(nèi)的尸體忽然破棺而出禀酱,到底是詐尸還是另有隱情,我是刑警寧澤牧嫉,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布剂跟,位于F島的核電站减途,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏曹洽。R本人自食惡果不足惜鳍置,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望送淆。 院中可真熱鬧墓捻,春花似錦、人聲如沸坊夫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环凿。三九已至梧兼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間智听,已是汗流浹背羽杰。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留到推,地道東北人考赛。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像莉测,于是被迫代替她去往敵國和親颜骤。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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