RelativeLayout和LinearLayout性能比较
RelativeLayout和LinearLayout性能比較 相對布局和線性布局的性能比較 【轉(zhuǎn)載請注明出處】 :http://blog.csdn.net/guyuealian/article/details/52162774 ?
?看到幾篇關(guān)于RelativeLayout和LinearLayout性能分析的博客,寫的相當(dāng)不錯(cuò),這里在大神的基礎(chǔ)上,增加了部分內(nèi)容RelativeLayout和LinearLayout是Android中常用的布局,兩者的使用會(huì)極大的影響程序生成每一幀的性能,因此,正確的使用它們是提升程序性能的重要工作。記得以前,較低的SDK版本新建Android項(xiàng)目時(shí),默認(rèn)的布局文件是采用線性布局LinearLayout,但現(xiàn)在自動(dòng)生成的布局文件都是RelativeLayout,或許你會(huì)認(rèn)為這是IDE的默認(rèn)設(shè)置問題,其實(shí)不然,這由 android-sdk\tools\templates\activities\BlankActivity\root\res\layout\activity_simple.xml.ftl 這個(gè)文件事先就定好了的,也就是說這是Google的選擇,而非IDE的選擇。那SDK為什么會(huì)默認(rèn)給開發(fā)者新建一個(gè)默認(rèn)的RelativeLayout布局呢?<-----原因見最后小結(jié)當(dāng)然是因?yàn)镽elativeLayout的性能更優(yōu),性能至上嘛。但是我們再看看默認(rèn)新建的這個(gè)RelativeLayout的父容器,也就是當(dāng)前窗口的頂級View——DecorView,它卻是個(gè)垂直方向的LinearLayout,上面是標(biāo)題欄,下面是內(nèi)容欄。那么問題來了,Google為什么給開發(fā)者默認(rèn)新建了個(gè)RelativeLayout,而自己卻偷偷用了個(gè)LinearLayout,到底誰的性能更高,開發(fā)者該怎么選擇呢? ? ? ? ?下面將通過分析它們的源碼來探討其View繪制性能,并得出其正確的使用方法。一、View的一些基本工作原理
? ? ? 先通過幾個(gè)問題,簡單的了解寫android中View的工作原理吧。 (1)View是什么?簡單來說,View是Android系統(tǒng)在屏幕上的視覺呈現(xiàn),也就是說你在手機(jī)屏幕上看到的東西都是View。 (2)View是怎么繪制出來的?View的繪制流程是從ViewRoot的performTraversals()方法開始,依次經(jīng)過measure(),layout()和draw()三個(gè)過程才最終將一個(gè)View繪制出來。 (3)View是怎么呈現(xiàn)在界面上的?Android中的視圖都是通過Window來呈現(xiàn)的,不管Activity、Dialog還是Toast它們都有一個(gè)Window,然后通過WindowManager來管理View。Window和頂級View——DecorView的通信是依賴ViewRoot完成的。 (4)View和ViewGroup什么區(qū)別?不管簡單的Button和TextView還是復(fù)雜的RelativeLayout和ListView,他們的共同基類都是View。所以說,View是一種界面層控件的抽象,他代表了一個(gè)控件。那ViewGroup是什么東西,它可以被翻譯成控件組,即一組View。ViewGroup也是繼承View,這就意味著View本身可以是單個(gè)控件,也可以是多個(gè)控件組成的控件組。根據(jù)這個(gè)理論,Button顯然是個(gè)View,而RelativeLayout不但是一個(gè)View還可以是一個(gè)ViewGroup,而ViewGroup內(nèi)部是可以有子View的,這個(gè)子View同樣也可能是ViewGroup,以此類推。二、RelativeLayout和LinearLayout性能PK
? ? ? ?基于以上原理和大背景,我們要探討的性能問題,說的簡單明了一點(diǎn)就是:當(dāng)RelativeLayout和LinearLayout分別作為ViewGroup,表達(dá)相同布局時(shí)繪制在屏幕上時(shí)誰更快一點(diǎn)。上面已經(jīng)簡單說了View的繪制,從ViewRoot的performTraversals()方法開始依次調(diào)用perfromMeasure、performLayout和performDraw這三個(gè)方法。這三個(gè)方法分別完成頂級View的measure、layout和draw三大流程,其中perfromMeasure會(huì)調(diào)用measure,measure又會(huì)調(diào)用onMeasure,在onMeasure方法中則會(huì)對所有子元素進(jìn)行measure,這個(gè)時(shí)候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程,接著子元素會(huì)重復(fù)父容器的measure,如此反復(fù)就完成了整個(gè)View樹的遍歷。同理,performLayout和performDraw也分別完成perfromMeasure類似的流程。通過這三大流程,分別遍歷整棵View樹,就實(shí)現(xiàn)了Measure,Layout,Draw這一過程,View就繪制出來了。那么我們就分別來追蹤下RelativeLayout和LinearLayout這三大流程的執(zhí)行耗時(shí)。
如下圖,我們分別用兩用種方式簡單的實(shí)現(xiàn)布局測試下
? ? ?根據(jù)上述關(guān)鍵代碼,RelativeLayout分別對所有子View進(jìn)行兩次measure,橫向縱向分別進(jìn)行一次,這是為什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依賴關(guān)系,而這個(gè)依賴關(guān)系可能和布局中View的順序并不相同,在確定每個(gè)子View的位置的時(shí)候,需要先給所有的子View排序一下。又因?yàn)镽elativeLayout允許A,B 2個(gè)子View,橫向上B依賴A,縱向上A依賴B。所以需要橫向縱向分別進(jìn)行一次排序測量。?mSortedHorizontalChildren和mSortedVerticalChildren是分別對水平方向的子控件和垂直方向的子控件進(jìn)行排序后的View數(shù)組。
(2)LinearLayout的onMeasure()方法 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}? ? ? 與RelativeLayout相比LinearLayout的measure就簡單的多,只需判斷線性布局是水平布局還是垂直布局即可,然后才進(jìn)行測量:
/*** Measures the children when the orientation of this LinearLayout is set* to {@link #VERTICAL}.** @param widthMeasureSpec Horizontal space requirements as imposed by the parent.* @param heightMeasureSpec Vertical space requirements as imposed by the parent.** @see #getOrientation()* @see #setOrientation(int)* @see #onMeasure(int, int)*/void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {mTotalLength = 0;int maxWidth = 0;int childState = 0;int alternativeMaxWidth = 0;int weightedMaxWidth = 0;boolean allFillParent = true;float totalWeight = 0;final int count = getVirtualChildCount();final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);boolean matchWidth = false;boolean skippedMeasure = false;final int baselineChildIndex = mBaselineAlignedChildIndex; final boolean useLargestChild = mUseLargestChild;int largestChildHeight = Integer.MIN_VALUE;// See how tall everyone is. Also remember max width.for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null) {mTotalLength += measureNullChild(i);continue;}if (child.getVisibility() == View.GONE) {i += getChildrenSkipCount(child, i);continue;}if (hasDividerBeforeChildAt(i)) {mTotalLength += mDividerHeight;}LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();totalWeight += lp.weight;if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {// Optimization: don't bother measuring children who are going to use// leftover space. These views will get measured again down below if// there is any leftover space.final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);skippedMeasure = true;} else {int oldHeight = Integer.MIN_VALUE;if (lp.height == 0 && lp.weight > 0) {// heightMode is either UNSPECIFIED or AT_MOST, and this// child wanted to stretch to fill available space.// Translate that to WRAP_CONTENT so that it does not end up// with a height of 0oldHeight = 0;lp.height = 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).measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec,totalWeight == 0 ? mTotalLength : 0);if (oldHeight != Integer.MIN_VALUE) {lp.height = oldHeight;}final int childHeight = child.getMeasuredHeight();final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));if (useLargestChild) {largestChildHeight = Math.max(childHeight, largestChildHeight);}}/*** If applicable, compute the additional offset to the child's baseline* we'll need later when asked {@link #getBaseline}.*/if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {mBaselineChildTop = mTotalLength;}// if we are trying to use a child index for our baseline, the above// book keeping only works if there are no children above it with// weight. fail fast to aid the developer.if (i < baselineChildIndex && lp.weight > 0) {throw new RuntimeException("A child of LinearLayout with index "+ "less than mBaselineAlignedChildIndex has weight > 0, which "+ "won't work. Either remove the weight, or don't set "+ "mBaselineAlignedChildIndex.");}boolean matchWidthLocally = false;if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {// The width of the linear layout will scale, and at least one// child said it wanted to match our width. Set a flag// indicating that we need to remeasure at least that view when// we know our width.matchWidth = true;matchWidthLocally = true;}final int margin = lp.leftMargin + lp.rightMargin;final int measuredWidth = child.getMeasuredWidth() + margin;maxWidth = Math.max(maxWidth, measuredWidth);childState = combineMeasuredStates(childState, child.getMeasuredState());allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;if (lp.weight > 0) {/** Widths of weighted Views are bogus if we end up* remeasuring, so keep them separate.*/weightedMaxWidth = Math.max(weightedMaxWidth,matchWidthLocally ? margin : measuredWidth);} else {alternativeMaxWidth = Math.max(alternativeMaxWidth,matchWidthLocally ? margin : measuredWidth);}i += getChildrenSkipCount(child, i);}if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {mTotalLength += mDividerHeight;}if (useLargestChild &&(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {mTotalLength = 0;for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null) {mTotalLength += measureNullChild(i);continue;}if (child.getVisibility() == GONE) {i += getChildrenSkipCount(child, i);continue;}final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)child.getLayoutParams();// Account for negative marginsfinal int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));}}// Add in our paddingmTotalLength += mPaddingTop + mPaddingBottom;int heightSize = mTotalLength;// Check against our minimum heightheightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightMeasureSpecint heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);heightSize = heightSizeAndState & MEASURED_SIZE_MASK;// Either expand children with weight to take up available space or// shrink them if they extend beyond our current bounds. If we skipped// measurement on any children, we need to measure them now.int delta = heightSize - mTotalLength;if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;mTotalLength = 0;for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child.getVisibility() == View.GONE) {continue;}LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();float childExtra = lp.weight;if (childExtra > 0) {// Child said it could absorb extra space -- give him his shareint share = (int) (childExtra * delta / weightSum);weightSum -= childExtra;delta -= share;final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,mPaddingLeft + mPaddingRight +lp.leftMargin + lp.rightMargin, lp.width);// TODO: Use a field like lp.isMeasured to figure out if this// child has been previously measuredif ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {// child was measured once already above...// base new measurement on stored valuesint childHeight = child.getMeasuredHeight() + share;if (childHeight < 0) {childHeight = 0;}child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));} else {// child was skipped in the loop above.// Measure for this first time here child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,MeasureSpec.EXACTLY));}// Child may now not fit in vertical dimension.childState = combineMeasuredStates(childState, child.getMeasuredState()& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));}final int margin = lp.leftMargin + lp.rightMargin;final int measuredWidth = child.getMeasuredWidth() + margin;maxWidth = Math.max(maxWidth, measuredWidth);boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&lp.width == LayoutParams.MATCH_PARENT;alternativeMaxWidth = Math.max(alternativeMaxWidth,matchWidthLocally ? margin : measuredWidth);allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));}// Add in our paddingmTotalLength += mPaddingTop + mPaddingBottom;// TODO: Should we recompute the heightSpec based on the new total length?} else {alternativeMaxWidth = Math.max(alternativeMaxWidth,weightedMaxWidth);// We have no limit, so make all weighted views as tall as the largest child.// Children will have already been measured once.if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null || child.getVisibility() == View.GONE) {continue;}final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();float childExtra = lp.weight;if (childExtra > 0) {child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(largestChildHeight,MeasureSpec.EXACTLY));}}}}if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {maxWidth = alternativeMaxWidth;}maxWidth += mPaddingLeft + mPaddingRight;// Check against our minimum widthmaxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);if (matchWidth) {forceUniformWidth(count, heightMeasureSpec);}}? ? ? LinearLayout首先會(huì)對所有的子View進(jìn)行measure,并計(jì)算totalWeight(所有子View的weight屬性之和),然后判斷子View的weight屬性是否為最大,如為最大則將剩余的空間分配給它。如果不使用weight屬性進(jìn)行布局,則不進(jìn)行第二次measure。
? ? ? 父視圖在對子視圖進(jìn)行measure操作的過程中,使用變量mTotalLength保存已經(jīng)measure過的child所占用的高度,該變量剛開始時(shí)是0。在for循環(huán)中調(diào)用measureChildBeforeLayout()對每一個(gè)child進(jìn)行測量,該函數(shù)實(shí)際上僅僅是調(diào)用了measureChildWithMargins(),在調(diào)用該方法時(shí),使用了兩個(gè)參數(shù)。其中一個(gè)是heightMeasureSpec,該參數(shù)為LinearLayout本身的measureSpec;另一個(gè)參數(shù)就是mTotalLength,代表該LinearLayout已經(jīng)被其子視圖所占用的高度。 每次for循環(huán)對child測量完畢后,調(diào)用child.getMeasuredHeight()獲取該子視圖最終的高度,并將這個(gè)高度添加到mTotalLength中。在本步驟中,暫時(shí)避開了lp.weight>0的子視圖,即暫時(shí)先不測量這些子視圖,因?yàn)楹竺鎸迅敢晥D剩余的高度按照weight值的大小平均分配給相應(yīng)的子視圖。源碼中使用了一個(gè)局部變量totalWeight累計(jì)所有子視圖的weight值。處理lp.weight>0的情況需要注意,如果變量heightMode是EXACTLY,那么,當(dāng)其他子視圖占滿父視圖的高度后,weight>0的子視圖可能分配不到布局空間,從而不被顯示,只有當(dāng)heightMode是AT_MOST或者UNSPECIFIED時(shí),weight>0的視圖才能優(yōu)先獲得布局高度。
? ? 最后我們的結(jié)論是:如果不使用weight屬性,LinearLayout會(huì)在當(dāng)前方向上進(jìn)行一次measure的過程,如果使用weight屬性,LinearLayout會(huì)避開設(shè)置過weight屬性的view做第一次measure,完了再對設(shè)置過weight屬性的view做第二次measure。由此可見,weight屬性對性能是有影響的,而且本身有大坑,請注意避讓。三、小結(jié)
從源碼中我們似乎能看出,我們先前的測試結(jié)果中RelativeLayout不如LinearLayout快的根本原因是RelativeLayout需要對其子View進(jìn)行兩次measure過程。而LinearLayout則只需一次measure過程,所以顯然會(huì)快于RelativeLayout,但是如果LinearLayout中有weight屬性,則也需要進(jìn)行兩次measure,但即便如此,應(yīng)該仍然會(huì)比RelativeLayout的情況好一點(diǎn)。 RelativeLayout另一個(gè)性能問題 對比到這里就結(jié)束了嘛?顯然沒有!我們再看看View的Measure()方法都干了些什么? public final void measure(int widthMeasureSpec, int heightMeasureSpec) {if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||widthMeasureSpec != mOldWidthMeasureSpec ||heightMeasureSpec != mOldHeightMeasureSpec) {......}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension} ? ? ?View的measure方法里對繪制過程做了一個(gè)優(yōu)化,如果我們或者我們的子View沒有要求強(qiáng)制刷新,而父View給子View的傳入值也沒有變化(也就是說子View的位置沒變化),就不會(huì)做無謂的measure。但是上面已經(jīng)說了RelativeLayout要做兩次measure,而在做橫向的測量時(shí),縱向的測量結(jié)果尚未完成,只好暫時(shí)使用myHeight傳入子View系統(tǒng),假如子View的Height不等于(設(shè)置了margin)myHeight的高度,那么measure中上面代碼所做得優(yōu)化將不起作用,這一過程將進(jìn)一步影響RelativeLayout的繪制性能。而LinearLayout則無這方面的擔(dān)憂。解決這個(gè)問題也很好辦,如果可以,盡量使用padding代替margin。結(jié)論
(1)RelativeLayout會(huì)讓子View調(diào)用2次onMeasure,LinearLayout 在有weight時(shí),也會(huì)調(diào)用子View 2次onMeasure (2)RelativeLayout的子View如果高度和RelativeLayout不同,則會(huì)引發(fā)效率問題,當(dāng)子View很復(fù)雜時(shí),這個(gè)問題會(huì)更加嚴(yán)重。如果可以,盡量使用padding代替margin。 (3)在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。 (4)提高繪制性能的使用方式根據(jù)上面源碼的分析,RelativeLayout將對所有的子View進(jìn)行兩次measure,而LinearLayout在使用weight屬性進(jìn)行布局時(shí)也會(huì)對子View進(jìn)行兩次measure,如果他們位于整個(gè)View樹的頂端時(shí)并可能進(jìn)行多層的嵌套時(shí),位于底層的View將會(huì)進(jìn)行大量的measure操作,大大降低程序性能。因此,應(yīng)盡量將RelativeLayout和LinearLayout置于View樹的底層,并減少嵌套。 最后思考一下文章開頭的疑問:較低的SDK版本新建Android項(xiàng)目時(shí),默認(rèn)的布局文件是采用線性布局LinearLayout,但現(xiàn)在自動(dòng)生成的布局文件都是RelativeLayout,為什么呢? 這是Google關(guān)于RelativeLayout的說明: A RelativeLayout is a very powerful utility for designing a user interface because it can eliminate nested view groups and keep your layout hierarchy flat, which improves performance. If you find yourself using several nested LinearLayout groups, you may be able to replace them with a single RelativeLayout. ? Google的意思是“性能至上”, RelativeLayout 在性能上更好,因?yàn)樵谥T如 ListView 等控件中,使用 LinearLayout 容易產(chǎn)生多層嵌套的布局結(jié)構(gòu),這在性能上是不好的。而 RelativeLayout 因其原理上的靈活性,通常層級結(jié)構(gòu)都比較扁平,很多使用LinearLayout 的情況都可以用一個(gè) RelativeLayout 來替代,以降低布局的嵌套層級,優(yōu)化性能。所以從這一點(diǎn)來看,Google比較推薦開發(fā)者使用RelativeLayout,因此就將其作為Blank Activity的默認(rèn)布局了。參考資料: 【1】《Android中RelativeLayout和LinearLayout性能分析》?http://www.jianshu.com/p/8a7d059da746? 【2】《Android RelativeLayout和LinearLayout性能分析》?http://www.tuicool.com/articles/uQ3MBnj?
總結(jié)
以上是生活随笔為你收集整理的RelativeLayout和LinearLayout性能比较的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 通知栏Notificat
- 下一篇: 内存可见性和原子性:Synchroniz