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这三大流程的执行耗时。
如下图,我们分别用两用种方式简单的实现布局测试下


LinearLayout
Measure:0.738ms
Layout:0.176ms
draw:7.655ms
RelativeLayout
Measure:2.280ms
Layout:0.153ms
draw:7.696ms
    从这个数据来看无论使用RelativeLayout还是LinearLayout,layout和draw的过程两者相差无几,考虑到误差的问题,几乎可以认为两者不分伯仲,关键是Measure的过程RelativeLayout却比LinearLayout慢了一大截。
(1)RelativeLayout的onMeasure()方法
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     if (mDirtyHierarchy) {  
  3.         mDirtyHierarchy = false;  
  4.         sortChildren();  
  5.     }  
  6.   
  7.     int myWidth = -1;  
  8.     int myHeight = -1;  
  9.   
  10.     int width = 0;  
  11.     int height = 0;  
  12.   
  13.     final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  14.     final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  15.     final int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  16.     final int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  17.   
  18.     // Record our dimensions if they are known;  
  19.     if (widthMode != MeasureSpec.UNSPECIFIED) {  
  20.         myWidth = widthSize;  
  21.     }  
  22.   
  23.     if (heightMode != MeasureSpec.UNSPECIFIED) {  
  24.         myHeight = heightSize;  
  25.     }  
  26.   
  27.     if (widthMode == MeasureSpec.EXACTLY) {  
  28.         width = myWidth;  
  29.     }  
  30.   
  31.     if (heightMode == MeasureSpec.EXACTLY) {  
  32.         height = myHeight;  
  33.     }  
  34.   
  35.     mHasBaselineAlignedChild = false;  
  36.   
  37.     View ignore = null;  
  38.     int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;  
  39.     final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;  
  40.     gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;  
  41.     final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;  
  42.   
  43.     int left = Integer.MAX_VALUE;  
  44.     int top = Integer.MAX_VALUE;  
  45.     int right = Integer.MIN_VALUE;  
  46.     int bottom = Integer.MIN_VALUE;  
  47.   
  48.     boolean offsetHorizontalAxis = false;  
  49.     boolean offsetVerticalAxis = false;  
  50.   
  51.     if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {  
  52.         ignore = findViewById(mIgnoreGravity);  
  53.     }  
  54.   
  55.     final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;  
  56.     final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;  
  57.   
  58.     // We need to know our size for doing the correct computation of children positioning in RTL  
  59.     // mode but there is no practical way to get it instead of running the code below.  
  60.     // So, instead of running the code twice, we just set the width to a "default display width"  
  61.     // before the computation and then, as a last pass, we will update their real position with  
  62.     // an offset equals to "DEFAULT_WIDTH - width".  
  63.     final int layoutDirection = getLayoutDirection();  
  64.     if (isLayoutRtl() && myWidth == -1) {  
  65.         myWidth = DEFAULT_WIDTH;  
  66.     }  
  67.   
  68.     View[] views = mSortedHorizontalChildren;  
  69.     int count = views.length;  
  70.   
  71.     for (int i = 0; i < count; i++) {  
  72.         View child = views[i];  
  73.         if (child.getVisibility() != GONE) {  
  74.             LayoutParams params = (LayoutParams) child.getLayoutParams();  
  75.             int[] rules = params.getRules(layoutDirection);  
  76.   
  77.             applyHorizontalSizeRules(params, myWidth, rules);  
  78.             measureChildHorizontal(child, params, myWidth, myHeight);  
  79.   
  80.             if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {  
  81.                 offsetHorizontalAxis = true;  
  82.             }  
  83.         }  
  84.     }  
  85.   
  86.     views = mSortedVerticalChildren;  
  87.     count = views.length;  
  88.     final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;  
  89.   
  90.     for (int i = 0; i < count; i++) {  
  91.         View child = views[i];  
  92.         if (child.getVisibility() != GONE) {  
  93.             LayoutParams params = (LayoutParams) child.getLayoutParams();  
  94.               
  95.             applyVerticalSizeRules(params, myHeight);  
  96.             measureChild(child, params, myWidth, myHeight);  
  97.             if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {  
  98.                 offsetVerticalAxis = true;  
  99.             }  
  100.   
  101.             if (isWrapContentWidth) {  
  102.                 if (isLayoutRtl()) {  
  103.                     if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {  
  104.                         width = Math.max(width, myWidth - params.mLeft);  
  105.                     } else {  
  106.                         width = Math.max(width, myWidth - params.mLeft - params.leftMargin);  
  107.                     }  
  108.                 } else {  
  109.                     if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {  
  110.                         width = Math.max(width, params.mRight);  
  111.                     } else {  
  112.                         width = Math.max(width, params.mRight + params.rightMargin);  
  113.                     }  
  114.                 }  
  115.             }  
  116.   
  117.             if (isWrapContentHeight) {  
  118.                 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {  
  119.                     height = Math.max(height, params.mBottom);  
  120.                 } else {  
  121.                     height = Math.max(height, params.mBottom + params.bottomMargin);  
  122.                 }  
  123.             }  
  124.   
  125.             if (child != ignore || verticalGravity) {  
  126.                 left = Math.min(left, params.mLeft - params.leftMargin);  
  127.                 top = Math.min(top, params.mTop - params.topMargin);  
  128.             }  
  129.   
  130.             if (child != ignore || horizontalGravity) {  
  131.                 right = Math.max(right, params.mRight + params.rightMargin);  
  132.                 bottom = Math.max(bottom, params.mBottom + params.bottomMargin);  
  133.             }  
  134.         }  
  135.     }  
  136.   
  137.     if (mHasBaselineAlignedChild) {  
  138.         for (int i = 0; i < count; i++) {  
  139.             View child = getChildAt(i);  
  140.             if (child.getVisibility() != GONE) {  
  141.                 LayoutParams params = (LayoutParams) child.getLayoutParams();  
  142.                 alignBaseline(child, params);  
  143.   
  144.                 if (child != ignore || verticalGravity) {  
  145.                     left = Math.min(left, params.mLeft - params.leftMargin);  
  146.                     top = Math.min(top, params.mTop - params.topMargin);  
  147.                 }  
  148.   
  149.                 if (child != ignore || horizontalGravity) {  
  150.                     right = Math.max(right, params.mRight + params.rightMargin);  
  151.                     bottom = Math.max(bottom, params.mBottom + params.bottomMargin);  
  152.                 }  
  153.             }  
  154.         }  
  155.     }  
  156.   
  157.     if (isWrapContentWidth) {  
  158.         // Width already has left padding in it since it was calculated by looking at  
  159.         // the right of each child view  
  160.         width += mPaddingRight;  
  161.   
  162.         if (mLayoutParams != null && mLayoutParams.width >= 0) {  
  163.             width = Math.max(width, mLayoutParams.width);  
  164.         }  
  165.   
  166.         width = Math.max(width, getSuggestedMinimumWidth());  
  167.         width = resolveSize(width, widthMeasureSpec);  
  168.   
  169.         if (offsetHorizontalAxis) {  
  170.             for (int i = 0; i < count; i++) {  
  171.                 View child = getChildAt(i);  
  172.                 if (child.getVisibility() != GONE) {  
  173.                     LayoutParams params = (LayoutParams) child.getLayoutParams();  
  174.                     final int[] rules = params.getRules(layoutDirection);  
  175.                     if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {  
  176.                         centerHorizontal(child, params, width);  
  177.                     } else if (rules[ALIGN_PARENT_RIGHT] != 0) {  
  178.                         final int childWidth = child.getMeasuredWidth();  
  179.                         params.mLeft = width - mPaddingRight - childWidth;  
  180.                         params.mRight = params.mLeft + childWidth;  
  181.                     }  
  182.                 }  
  183.             }  
  184.         }  
  185.     }  
  186.   
  187.     if (isWrapContentHeight) {  
  188.         // Height already has top padding in it since it was calculated by looking at  
  189.         // the bottom of each child view  
  190.         height += mPaddingBottom;  
  191.   
  192.         if (mLayoutParams != null && mLayoutParams.height >= 0) {  
  193.             height = Math.max(height, mLayoutParams.height);  
  194.         }  
  195.   
  196.         height = Math.max(height, getSuggestedMinimumHeight());  
  197.         height = resolveSize(height, heightMeasureSpec);  
  198.   
  199.         if (offsetVerticalAxis) {  
  200.             for (int i = 0; i < count; i++) {  
  201.                 View child = getChildAt(i);  
  202.                 if (child.getVisibility() != GONE) {  
  203.                     LayoutParams params = (LayoutParams) child.getLayoutParams();  
  204.                     final int[] rules = params.getRules(layoutDirection);  
  205.                     if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {  
  206.                         centerVertical(child, params, height);  
  207.                     } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {  
  208.                         final int childHeight = child.getMeasuredHeight();  
  209.                         params.mTop = height - mPaddingBottom - childHeight;  
  210.                         params.mBottom = params.mTop + childHeight;  
  211.                     }  
  212.                 }  
  213.             }  
  214.         }  
  215.     }  
  216.   
  217.     if (horizontalGravity || verticalGravity) {  
  218.         final Rect selfBounds = mSelfBounds;  
  219.         selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,  
  220.                 height - mPaddingBottom);  
  221.   
  222.         final Rect contentBounds = mContentBounds;  
  223.         Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,  
  224.                 layoutDirection);  
  225.   
  226.         final int horizontalOffset = contentBounds.left - left;  
  227.         final int verticalOffset = contentBounds.top - top;  
  228.         if (horizontalOffset != 0 || verticalOffset != 0) {  
  229.             for (int i = 0; i < count; i++) {  
  230.                 View child = getChildAt(i);  
  231.                 if (child.getVisibility() != GONE && child != ignore) {  
  232.                     LayoutParams params = (LayoutParams) child.getLayoutParams();  
  233.                     if (horizontalGravity) {  
  234.                         params.mLeft += horizontalOffset;  
  235.                         params.mRight += horizontalOffset;  
  236.                     }  
  237.                     if (verticalGravity) {  
  238.                         params.mTop += verticalOffset;  
  239.                         params.mBottom += verticalOffset;  
  240.                     }  
  241.                 }  
  242.             }  
  243.         }  
  244.     }  
  245.   
  246.     if (isLayoutRtl()) {  
  247.         final int offsetWidth = myWidth - width;  
  248.         for (int i = 0; i < count; i++) {  
  249.             View child = getChildAt(i);  
  250.             if (child.getVisibility() != GONE) {  
  251.                 LayoutParams params = (LayoutParams) child.getLayoutParams();  
  252.                 params.mLeft -= offsetWidth;  
  253.                 params.mRight -= offsetWidth;  
  254.             }  
  255.         }  
  256.   
  257.     }  
  258.   
  259.     setMeasuredDimension(width, height);  
  260. }  

     根据上述关键代码,RelativeLayout分别对所有子View进行两次measure,横向纵向分别进行一次,这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中View的顺序并不相同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许A,B 2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向分别进行一次排序测量。 mSortedHorizontalChildren和mSortedVerticalChildren是分别对水平方向的子控件和垂直方向的子控件进行排序后的View数组。

(2)LinearLayout的onMeasure()方法
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     if (mOrientation == VERTICAL) {  
  4.         measureVertical(widthMeasureSpec, heightMeasureSpec);  
  5.     } else {  
  6.         measureHorizontal(widthMeasureSpec, heightMeasureSpec);  
  7.     }  
  8. }  

      与RelativeLayout相比LinearLayout的measure就简单的多,只需判断线性布局是水平布局还是垂直布局即可,然后才进行测量:

  1. /** 
  2.  * Measures the children when the orientation of this LinearLayout is set 
  3.  * to {@link #VERTICAL}. 
  4.  * 
  5.  * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. 
  6.  * @param heightMeasureSpec Vertical space requirements as imposed by the parent. 
  7.  * 
  8.  * @see #getOrientation() 
  9.  * @see #setOrientation(int) 
  10.  * @see #onMeasure(int, int) 
  11.  */  
  12. void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
  13.     mTotalLength = 0;  
  14.     int maxWidth = 0;  
  15.     int childState = 0;  
  16.     int alternativeMaxWidth = 0;  
  17.     int weightedMaxWidth = 0;  
  18.     boolean allFillParent = true;  
  19.     float totalWeight = 0;  
  20.   
  21.     final int count = getVirtualChildCount();  
  22.       
  23.     final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  24.     final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  25.   
  26.     boolean matchWidth = false;  
  27.     boolean skippedMeasure = false;  
  28.   
  29.     final int baselineChildIndex = mBaselineAlignedChildIndex;          
  30.     final boolean useLargestChild = mUseLargestChild;  
  31.   
  32.     int largestChildHeight = Integer.MIN_VALUE;  
  33.   
  34.     // See how tall everyone is. Also remember max width.  
  35.     for (int i = 0; i < count; ++i) {  
  36.         final View child = getVirtualChildAt(i);  
  37.   
  38.         if (child == null) {  
  39.             mTotalLength += measureNullChild(i);  
  40.             continue;  
  41.         }  
  42.   
  43.         if (child.getVisibility() == View.GONE) {  
  44.            i += getChildrenSkipCount(child, i);  
  45.            continue;  
  46.         }  
  47.   
  48.         if (hasDividerBeforeChildAt(i)) {  
  49.             mTotalLength += mDividerHeight;  
  50.         }  
  51.   
  52.         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
  53.   
  54.         totalWeight += lp.weight;  
  55.           
  56.         if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
  57.             // Optimization: don't bother measuring children who are going to use  
  58.             // leftover space. These views will get measured again down below if  
  59.             // there is any leftover space.  
  60.             final int totalLength = mTotalLength;  
  61.             mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);  
  62.             skippedMeasure = true;  
  63.         } else {  
  64.             int oldHeight = Integer.MIN_VALUE;  
  65.   
  66.             if (lp.height == 0 && lp.weight > 0) {  
  67.                 // heightMode is either UNSPECIFIED or AT_MOST, and this  
  68.                 // child wanted to stretch to fill available space.  
  69.                 // Translate that to WRAP_CONTENT so that it does not end up  
  70.                 // with a height of 0  
  71.                 oldHeight = 0;  
  72.                 lp.height = LayoutParams.WRAP_CONTENT;  
  73.             }  
  74.   
  75.             // Determine how big this child would like to be. If this or  
  76.             // previous children have given a weight, then we allow it to  
  77.             // use all available space (and we will shrink things later  
  78.             // if needed).  
  79.             measureChildBeforeLayout(  
  80.                    child, i, widthMeasureSpec, 0, heightMeasureSpec,  
  81.                    totalWeight == 0 ? mTotalLength : 0);  
  82.   
  83.             if (oldHeight != Integer.MIN_VALUE) {  
  84.                lp.height = oldHeight;  
  85.             }  
  86.   
  87.             final int childHeight = child.getMeasuredHeight();  
  88.             final int totalLength = mTotalLength;  
  89.             mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +  
  90.                    lp.bottomMargin + getNextLocationOffset(child));  
  91.   
  92.             if (useLargestChild) {  
  93.                 largestChildHeight = Math.max(childHeight, largestChildHeight);  
  94.             }  
  95.         }  
  96.   
  97.         /** 
  98.          * If applicable, compute the additional offset to the child's baseline 
  99.          * we'll need later when asked {@link #getBaseline}. 
  100.          */  
  101.         if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {  
  102.            mBaselineChildTop = mTotalLength;  
  103.         }  
  104.   
  105.         // if we are trying to use a child index for our baseline, the above  
  106.         // book keeping only works if there are no children above it with  
  107.         // weight.  fail fast to aid the developer.  
  108.         if (i < baselineChildIndex && lp.weight > 0) {  
  109.             throw new RuntimeException("A child of LinearLayout with index "  
  110.                     + "less than mBaselineAlignedChildIndex has weight > 0, which "  
  111.                     + "won't work.  Either remove the weight, or don't set "  
  112.                     + "mBaselineAlignedChildIndex.");  
  113.         }  
  114.   
  115.         boolean matchWidthLocally = false;  
  116.         if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {  
  117.             // The width of the linear layout will scale, and at least one  
  118.             // child said it wanted to match our width. Set a flag  
  119.             // indicating that we need to remeasure at least that view when  
  120.             // we know our width.  
  121.             matchWidth = true;  
  122.             matchWidthLocally = true;  
  123.         }  
  124.   
  125.         final int margin = lp.leftMargin + lp.rightMargin;  
  126.         final int measuredWidth = child.getMeasuredWidth() + margin;  
  127.         maxWidth = Math.max(maxWidth, measuredWidth);  
  128.         childState = combineMeasuredStates(childState, child.getMeasuredState());  
  129.   
  130.         allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;  
  131.         if (lp.weight > 0) {  
  132.             /* 
  133.              * Widths of weighted Views are bogus if we end up 
  134.              * remeasuring, so keep them separate. 
  135.              */  
  136.             weightedMaxWidth = Math.max(weightedMaxWidth,  
  137.                     matchWidthLocally ? margin : measuredWidth);  
  138.         } else {  
  139.             alternativeMaxWidth = Math.max(alternativeMaxWidth,  
  140.                     matchWidthLocally ? margin : measuredWidth);  
  141.         }  
  142.   
  143.         i += getChildrenSkipCount(child, i);  
  144.     }  
  145.   
  146.     if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {  
  147.         mTotalLength += mDividerHeight;  
  148.     }  
  149.   
  150.     if (useLargestChild &&  
  151.             (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {  
  152.         mTotalLength = 0;  
  153.   
  154.         for (int i = 0; i < count; ++i) {  
  155.             final View child = getVirtualChildAt(i);  
  156.   
  157.             if (child == null) {  
  158.                 mTotalLength += measureNullChild(i);  
  159.                 continue;  
  160.             }  
  161.   
  162.             if (child.getVisibility() == GONE) {  
  163.                 i += getChildrenSkipCount(child, i);  
  164.                 continue;  
  165.             }  
  166.   
  167.             final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)  
  168.                     child.getLayoutParams();  
  169.             // Account for negative margins  
  170.             final int totalLength = mTotalLength;  
  171.             mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +  
  172.                     lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));  
  173.         }  
  174.     }  
  175.   
  176.     // Add in our padding  
  177.     mTotalLength += mPaddingTop + mPaddingBottom;  
  178.   
  179.     int heightSize = mTotalLength;  
  180.   
  181.     // Check against our minimum height  
  182.     heightSize = Math.max(heightSize, getSuggestedMinimumHeight());  
  183.       
  184.     // Reconcile our calculated size with the heightMeasureSpec  
  185.     int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);  
  186.     heightSize = heightSizeAndState & MEASURED_SIZE_MASK;  
  187.       
  188.     // Either expand children with weight to take up available space or  
  189.     // shrink them if they extend beyond our current bounds. If we skipped  
  190.     // measurement on any children, we need to measure them now.  
  191.     int delta = heightSize - mTotalLength;  
  192.     if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {  
  193.         float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;  
  194.   
  195.         mTotalLength = 0;  
  196.   
  197.         for (int i = 0; i < count; ++i) {  
  198.             final View child = getVirtualChildAt(i);  
  199.               
  200.             if (child.getVisibility() == View.GONE) {  
  201.                 continue;  
  202.             }  
  203.               
  204.             LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
  205.               
  206.             float childExtra = lp.weight;  
  207.             if (childExtra > 0) {  
  208.                 // Child said it could absorb extra space -- give him his share  
  209.                 int share = (int) (childExtra * delta / weightSum);  
  210.                 weightSum -= childExtra;  
  211.                 delta -= share;  
  212.   
  213.                 final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,  
  214.                         mPaddingLeft + mPaddingRight +  
  215.                                 lp.leftMargin + lp.rightMargin, lp.width);  
  216.   
  217.                 // TODO: Use a field like lp.isMeasured to figure out if this  
  218.                 // child has been previously measured  
  219.                 if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {  
  220.                     // child was measured once already above...  
  221.                     // base new measurement on stored values  
  222.                     int childHeight = child.getMeasuredHeight() + share;  
  223.                     if (childHeight < 0) {  
  224.                         childHeight = 0;  
  225.                     }  
  226.                       
  227.                     child.measure(childWidthMeasureSpec,  
  228.                             MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));  
  229.                 } else {  
  230.                     // child was skipped in the loop above.  
  231.                     // Measure for this first time here        
  232.                     child.measure(childWidthMeasureSpec,  
  233.                             MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,  
  234.                                     MeasureSpec.EXACTLY));  
  235.                 }  
  236.   
  237.                 // Child may now not fit in vertical dimension.  
  238.                 childState = combineMeasuredStates(childState, child.getMeasuredState()  
  239.                         & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));  
  240.             }  
  241.   
  242.             final int margin =  lp.leftMargin + lp.rightMargin;  
  243.             final int measuredWidth = child.getMeasuredWidth() + margin;  
  244.             maxWidth = Math.max(maxWidth, measuredWidth);  
  245.   
  246.             boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&  
  247.                     lp.width == LayoutParams.MATCH_PARENT;  
  248.   
  249.             alternativeMaxWidth = Math.max(alternativeMaxWidth,  
  250.                     matchWidthLocally ? margin : measuredWidth);  
  251.   
  252.             allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;  
  253.   
  254.             final int totalLength = mTotalLength;  
  255.             mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +  
  256.                     lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));  
  257.         }  
  258.   
  259.         // Add in our padding  
  260.         mTotalLength += mPaddingTop + mPaddingBottom;  
  261.         // TODO: Should we recompute the heightSpec based on the new total length?  
  262.     } else {  
  263.         alternativeMaxWidth = Math.max(alternativeMaxWidth,  
  264.                                        weightedMaxWidth);  
  265.   
  266.   
  267.         // We have no limit, so make all weighted views as tall as the largest child.  
  268.         // Children will have already been measured once.  
  269.         if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {  
  270.             for (int i = 0; i < count; i++) {  
  271.                 final View child = getVirtualChildAt(i);  
  272.   
  273.                 if (child == null || child.getVisibility() == View.GONE) {  
  274.                     continue;  
  275.                 }  
  276.   
  277.                 final LinearLayout.LayoutParams lp =  
  278.                         (LinearLayout.LayoutParams) child.getLayoutParams();  
  279.   
  280.                 float childExtra = lp.weight;  
  281.                 if (childExtra > 0) {  
  282.                     child.measure(  
  283.                             MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),  
  284.                                     MeasureSpec.EXACTLY),  
  285.                             MeasureSpec.makeMeasureSpec(largestChildHeight,  
  286.                                     MeasureSpec.EXACTLY));  
  287.                 }  
  288.             }  
  289.         }  
  290.     }  
  291.   
  292.     if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {  
  293.         maxWidth = alternativeMaxWidth;  
  294.     }  
  295.       
  296.     maxWidth += mPaddingLeft + mPaddingRight;  
  297.   
  298.     // Check against our minimum width  
  299.     maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());  
  300.       
  301.     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),  
  302.             heightSizeAndState);  
  303.   
  304.     if (matchWidth) {  
  305.         forceUniformWidth(count, heightMeasureSpec);  
  306.     }  
  307. }  

      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()方法都干了些什么?
<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><strong>[java]</strong> <a target=_blank href="http://blog.csdn.net/guyuealian/article/details/52162774#" class="ViewSource" title="view plain">view plain</a><span data-mod="popu_168"> <a target=_blank href="http://blog.csdn.net/guyuealian/article/details/52162774#" class="CopyToClipboard" title="copy">copy</a></span><span data-mod="popu_169"> </span><span class="tracking-ad" data-mod="popu_167"><a target=_blank href="https://code.csdn.net/snippets/1818639" target="_blank" title="在CODE上查看代码片" style="text-indent:0;"><img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" style="position:relative;top:1px;left:2px;" width="12" height="12" /></a></span><span class="tracking-ad" data-mod="popu_170"><a target=_blank href="https://code.csdn.net/snippets/1818639/fork" target="_blank" title="派生到我的代码片" style="text-indent:0;"><img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" style="position:relative;top:2px;left:2px;" width="12" height="12" /></a></span></div></div><ol class="dp-j" start="1"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">final</span><span> </span><span class="keyword">void</span><span> measure(</span><span class="keyword">int</span><span> widthMeasureSpec, </span><span class="keyword">int</span><span> heightMeasureSpec) {  </span></span></li><li><span>    <span class="keyword">if</span><span> ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||  </span></span></li><li class="alt"><span>        widthMeasureSpec != mOldWidthMeasureSpec ||  </span></li><li><span>        heightMeasureSpec != mOldHeightMeasureSpec) {  </span></li><li class="alt"><span>                     ......  </span></li><li><span>      }  </span></li><li class="alt"><span>       mOldWidthMeasureSpec = widthMeasureSpec;  </span></li><li><span>    mOldHeightMeasureSpec = heightMeasureSpec;  </span></li><li class="alt"><span>  </span></li><li><span>    mMeasureCache.put(key, ((<span class="keyword">long</span><span>) mMeasuredWidth) << </span><span class="number">32</span><span> |  </span></span></li><li class="alt"><span>        (<span class="keyword">long</span><span>) mMeasuredHeight & 0xffffffffL); </span><span class="comment">// suppress sign extension</span><span>  </span></span></li><li><span>  }  </span></li></ol></div>
     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的说明:

  
  
[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 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
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值