RelativeLayout和LinearLayout性能比较
RelativeLayout和LinearLayout性能比較 相對布局和線性布局的性能比較 【轉載請注明出處】 :http://blog.csdn.net/guyuealian/article/details/52162774 ?
?看到幾篇關于RelativeLayout和LinearLayout性能分析的博客,寫的相當不錯,這里在大神的基礎上,增加了部分內容RelativeLayout和LinearLayout是Android中常用的布局,兩者的使用會極大的影響程序生成每一幀的性能,因此,正確的使用它們是提升程序性能的重要工作。記得以前,較低的SDK版本新建Android項目時,默認的布局文件是采用線性布局LinearLayout,但現在自動生成的布局文件都是RelativeLayout,或許你會認為這是IDE的默認設置問題,其實不然,這由 android-sdk\tools\templates\activities\BlankActivity\root\res\layout\activity_simple.xml.ftl 這個文件事先就定好了的,也就是說這是Google的選擇,而非IDE的選擇。那SDK為什么會默認給開發者新建一個默認的RelativeLayout布局呢?<-----原因見最后小結當然是因為RelativeLayout的性能更優,性能至上嘛。但是我們再看看默認新建的這個RelativeLayout的父容器,也就是當前窗口的頂級View——DecorView,它卻是個垂直方向的LinearLayout,上面是標題欄,下面是內容欄。那么問題來了,Google為什么給開發者默認新建了個RelativeLayout,而自己卻偷偷用了個LinearLayout,到底誰的性能更高,開發者該怎么選擇呢? ? ? ? ?下面將通過分析它們的源碼來探討其View繪制性能,并得出其正確的使用方法。一、View的一些基本工作原理
? ? ? 先通過幾個問題,簡單的了解寫android中View的工作原理吧。 (1)View是什么?簡單來說,View是Android系統在屏幕上的視覺呈現,也就是說你在手機屏幕上看到的東西都是View。 (2)View是怎么繪制出來的?View的繪制流程是從ViewRoot的performTraversals()方法開始,依次經過measure(),layout()和draw()三個過程才最終將一個View繪制出來。 (3)View是怎么呈現在界面上的?Android中的視圖都是通過Window來呈現的,不管Activity、Dialog還是Toast它們都有一個Window,然后通過WindowManager來管理View。Window和頂級View——DecorView的通信是依賴ViewRoot完成的。 (4)View和ViewGroup什么區別?不管簡單的Button和TextView還是復雜的RelativeLayout和ListView,他們的共同基類都是View。所以說,View是一種界面層控件的抽象,他代表了一個控件。那ViewGroup是什么東西,它可以被翻譯成控件組,即一組View。ViewGroup也是繼承View,這就意味著View本身可以是單個控件,也可以是多個控件組成的控件組。根據這個理論,Button顯然是個View,而RelativeLayout不但是一個View還可以是一個ViewGroup,而ViewGroup內部是可以有子View的,這個子View同樣也可能是ViewGroup,以此類推。二、RelativeLayout和LinearLayout性能PK
? ? ? ?基于以上原理和大背景,我們要探討的性能問題,說的簡單明了一點就是:當RelativeLayout和LinearLayout分別作為ViewGroup,表達相同布局時繪制在屏幕上時誰更快一點。上面已經簡單說了View的繪制,從ViewRoot的performTraversals()方法開始依次調用perfromMeasure、performLayout和performDraw這三個方法。這三個方法分別完成頂級View的measure、layout和draw三大流程,其中perfromMeasure會調用measure,measure又會調用onMeasure,在onMeasure方法中則會對所有子元素進行measure,這個時候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程,接著子元素會重復父容器的measure,如此反復就完成了整個View樹的遍歷。同理,performLayout和performDraw也分別完成perfromMeasure類似的流程。通過這三大流程,分別遍歷整棵View樹,就實現了Measure,Layout,Draw這一過程,View就繪制出來了。那么我們就分別來追蹤下RelativeLayout和LinearLayout這三大流程的執行耗時。
如下圖,我們分別用兩用種方式簡單的實現布局測試下
? ? ?根據上述關鍵代碼,RelativeLayout分別對所有子View進行兩次measure,橫向縱向分別進行一次,這是為什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依賴關系,而這個依賴關系可能和布局中View的順序并不相同,在確定每個子View的位置的時候,需要先給所有的子View排序一下。又因為RelativeLayout允許A,B 2個子View,橫向上B依賴A,縱向上A依賴B。所以需要橫向縱向分別進行一次排序測量。?mSortedHorizontalChildren和mSortedVerticalChildren是分別對水平方向的子控件和垂直方向的子控件進行排序后的View數組。
(2)LinearLayout的onMeasure()方法 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}? ? ? 與RelativeLayout相比LinearLayout的measure就簡單的多,只需判斷線性布局是水平布局還是垂直布局即可,然后才進行測量:
/*** 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首先會對所有的子View進行measure,并計算totalWeight(所有子View的weight屬性之和),然后判斷子View的weight屬性是否為最大,如為最大則將剩余的空間分配給它。如果不使用weight屬性進行布局,則不進行第二次measure。
? ? ? 父視圖在對子視圖進行measure操作的過程中,使用變量mTotalLength保存已經measure過的child所占用的高度,該變量剛開始時是0。在for循環中調用measureChildBeforeLayout()對每一個child進行測量,該函數實際上僅僅是調用了measureChildWithMargins(),在調用該方法時,使用了兩個參數。其中一個是heightMeasureSpec,該參數為LinearLayout本身的measureSpec;另一個參數就是mTotalLength,代表該LinearLayout已經被其子視圖所占用的高度。 每次for循環對child測量完畢后,調用child.getMeasuredHeight()獲取該子視圖最終的高度,并將這個高度添加到mTotalLength中。在本步驟中,暫時避開了lp.weight>0的子視圖,即暫時先不測量這些子視圖,因為后面將把父視圖剩余的高度按照weight值的大小平均分配給相應的子視圖。源碼中使用了一個局部變量totalWeight累計所有子視圖的weight值。處理lp.weight>0的情況需要注意,如果變量heightMode是EXACTLY,那么,當其他子視圖占滿父視圖的高度后,weight>0的子視圖可能分配不到布局空間,從而不被顯示,只有當heightMode是AT_MOST或者UNSPECIFIED時,weight>0的視圖才能優先獲得布局高度。
? ? 最后我們的結論是:如果不使用weight屬性,LinearLayout會在當前方向上進行一次measure的過程,如果使用weight屬性,LinearLayout會避開設置過weight屬性的view做第一次measure,完了再對設置過weight屬性的view做第二次measure。由此可見,weight屬性對性能是有影響的,而且本身有大坑,請注意避讓。三、小結
從源碼中我們似乎能看出,我們先前的測試結果中RelativeLayout不如LinearLayout快的根本原因是RelativeLayout需要對其子View進行兩次measure過程。而LinearLayout則只需一次measure過程,所以顯然會快于RelativeLayout,但是如果LinearLayout中有weight屬性,則也需要進行兩次measure,但即便如此,應該仍然會比RelativeLayout的情況好一點。 RelativeLayout另一個性能問題 對比到這里就結束了嘛?顯然沒有!我們再看看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方法里對繪制過程做了一個優化,如果我們或者我們的子View沒有要求強制刷新,而父View給子View的傳入值也沒有變化(也就是說子View的位置沒變化),就不會做無謂的measure。但是上面已經說了RelativeLayout要做兩次measure,而在做橫向的測量時,縱向的測量結果尚未完成,只好暫時使用myHeight傳入子View系統,假如子View的Height不等于(設置了margin)myHeight的高度,那么measure中上面代碼所做得優化將不起作用,這一過程將進一步影響RelativeLayout的繪制性能。而LinearLayout則無這方面的擔憂。解決這個問題也很好辦,如果可以,盡量使用padding代替margin。結論
(1)RelativeLayout會讓子View調用2次onMeasure,LinearLayout 在有weight時,也會調用子View 2次onMeasure (2)RelativeLayout的子View如果高度和RelativeLayout不同,則會引發效率問題,當子View很復雜時,這個問題會更加嚴重。如果可以,盡量使用padding代替margin。 (3)在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。 (4)提高繪制性能的使用方式根據上面源碼的分析,RelativeLayout將對所有的子View進行兩次measure,而LinearLayout在使用weight屬性進行布局時也會對子View進行兩次measure,如果他們位于整個View樹的頂端時并可能進行多層的嵌套時,位于底層的View將會進行大量的measure操作,大大降低程序性能。因此,應盡量將RelativeLayout和LinearLayout置于View樹的底層,并減少嵌套。 最后思考一下文章開頭的疑問:較低的SDK版本新建Android項目時,默認的布局文件是采用線性布局LinearLayout,但現在自動生成的布局文件都是RelativeLayout,為什么呢? 這是Google關于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 在性能上更好,因為在諸如 ListView 等控件中,使用 LinearLayout 容易產生多層嵌套的布局結構,這在性能上是不好的。而 RelativeLayout 因其原理上的靈活性,通常層級結構都比較扁平,很多使用LinearLayout 的情況都可以用一個 RelativeLayout 來替代,以降低布局的嵌套層級,優化性能。所以從這一點來看,Google比較推薦開發者使用RelativeLayout,因此就將其作為Blank Activity的默認布局了。參考資料: 【1】《Android中RelativeLayout和LinearLayout性能分析》?http://www.jianshu.com/p/8a7d059da746? 【2】《Android RelativeLayout和LinearLayout性能分析》?http://www.tuicool.com/articles/uQ3MBnj?
總結
以上是生活随笔為你收集整理的RelativeLayout和LinearLayout性能比较的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 通知栏Notificat
- 下一篇: 设计模式:回调模式