Android开发——LinearLayout和RelativeLayout的性能对比

来源:互联网 发布:淘宝卖家阿里妈妈 编辑:程序博客网 时间:2024/05/18 03:04

转载地址:http://blog.csdn.net/seu_calvin/article/details/53047682

0. 前言

我们都知道新建一个Android项目自动生成的Xml布局文件的根节点默认是RelativeLayout,这不是IDE默认设置,而是由android-sdk\tools\templates\activities\EmptyActivity\root\res\layout\activity_simple.xml.ftl这个文件事先就定好了的,在我们的理解里貌似LinearLayout的性能是要比RelativeLayout更优的,那SDK为什么会默认给开发者新建一个RelativeLayout呢?

同时作为顶级ViewDecorView却是个垂直方向的LinearLayout,上面是标题栏,下面是内容栏,我们常用的setContentView()方法就是给内容栏设置布局。那么LinearLayoutRelativeLayout谁的性能更高呢,好吧其实我们都知道前者性能更高,那原因是什么呢?

 

1.   性能对比

问题的核心在于,当RelativeLayoutLinearLayout分别作为ViewGroup表达相同布局时谁的绘制过程更快一点。

Hierarchy Viewer是随Android SDK发布的工具,位于Android SDK/tools/hierarchyviewer.bat,使用它可以来检测View绘制的三大过程的耗时。如果不清楚View绘制的三大过程的,可以参考我之前写过的 Android开发——View绘制过程源码解析中详细的介绍过了,这里就不再赘述了。

通过网上的很多实验结果我们得之,两者绘制同样的界面时layoutdraw的过程时间消耗相差无几,关键在于measure过程RelativeLayoutLinearLayout慢了一些。我们知道ViewGroup是没有onMeasure方法的,这个方法是交给子类自己实现的。因为不同的ViewGroup子类布局都不一样,那么onMeasure索性就全部交给他们自己实现好了。

所以我们就分别来追踪下RelativeLayoutLinearLayoutonMeasure过程来探索耗时问题的根源。本文原创,为保证错误及时更新请认准原创链接SEU_Calvin的博客。


1.1    RelativeLayoutonMeasure分析

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. @Override    
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  3. //...  
  4. View[] views = mSortedHorizontalChildren;  
  5. int count = views.length;  
  6. for (int i = 0; i < count; i++) {  
  7.     View child = views[i];  
  8.     if (child.getVisibility() != GONE) {  
  9.          LayoutParams params = (LayoutParams) child.getLayoutParams();  
  10.          applyHorizontalSizeRules(params, myWidth);  
  11.          measureChildHorizontal(child, params, myWidth, myHeight);  
  12.          if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {  
  13.                offsetHorizontalAxis = true;  
  14.          }  
  15.     }  
  16. }  
  17.   
  18. views = mSortedVerticalChildren;  
  19. count = views.length;  
  20. for (int i = 0; i < count; i++) {  
  21.      View child = views[i];  
  22.      if (child.getVisibility() != GONE) {  
  23.            LayoutParams params = (LayoutParams) child.getLayoutParams();  
  24.            applyVerticalSizeRules(params, myHeight);  
  25.            measureChild(child, params, myWidth, myHeight);  
  26.            if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {  
  27.                  offsetVerticalAxis = true;  
  28.            }  
  29.            if (isWrapContentWidth) {  
  30.                  width = Math.max(width, params.mRight);  
  31.            }  
  32.            if (isWrapContentHeight) {  
  33.                  height = Math.max(height, params.mBottom);  
  34.            }  
  35.            if (child != ignore || verticalGravity) {  
  36.                  left = Math.min(left, params.mLeft - params.leftMargin);  
  37.                  top = Math.min(top, params.mTop - params.topMargin);  
  38.            }  
  39.            if (child != ignore || horizontalGravity) {  
  40.                  right = Math.max(right, params.mRight + params.rightMargin);  
  41.                  bottom = Math.max(bottom, params.mBottom + params.bottomMargin);  
  42.            }  
  43.        }  
  44.   }  
  45.   //...  
  46. }  

根据源码我们发现RelativeLayout会根据2次排列的结果对子View各做一次measure。这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和Xml布局中View的顺序不同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许ViewB在横向上依赖ViewAViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。


同时需要注意的是View.measure()方法存在以下优化:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.         ...  
  6.         mOldWidthMeasureSpec = widthMeasureSpec;  
  7.         mOldHeightMeasureSpec = heightMeasureSpec;  
  8. }  

即如果我们或者我们的子View没有要求强制刷新,而父View给子View传入的值也没有变化(也就是说子View的位置没变化),就不会做无谓的测量RelativeLayoutonMeasure中做横向测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统。这样会导致在子View的高度和RelativeLayout的高度不相同时(设置了Margin),上述优化会失效,在View系统足够复杂时,效率问题就会很明显。


1.2  LinearLayoutonMeasure过程

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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. }    
  9. //LinearLayout会先做一个简单横纵方向判断,我们选择纵向这种情况继续分析  
  10. void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
  11. //...  
  12. for (int i = 0; i < count; ++i) {    
  13.       final View child = getVirtualChildAt(i);    
  14.       //... child为空、Gone以及分界线的情况略去  
  15.      //累计权重  
  16.       LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();    
  17.       totalWeight += lp.weight;    
  18.       //计算  
  19.       if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {    
  20.             //精确模式的情况下,子控件layout_height=0dp且weight大于0无法计算子控件的高度  
  21.             //但是可以先把margin值合入到总值中,后面根据剩余空间及权值再重新计算对应的高度  
  22.             final int totalLength = mTotalLength;    
  23.             mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);    
  24.       } else {    
  25.            if (lp.height == 0 && lp.weight > 0) {    
  26.             //如果这个条件成立,就代表 heightMode不是精确测量以及wrap_conent模式  
  27.             //也就是说布局是越小越好,你还想利用权值多分剩余空间是不可能的,只设为wrap_content模式  
  28.                  lp.height = LayoutParams.WRAP_CONTENT;    
  29.            }    
  30.     
  31.           // 子控件测量  
  32.           measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);           
  33.           //获取该子视图最终的高度,并将这个高度添加到mTotalLength中  
  34.           final int childHeight = child.getMeasuredHeight();    
  35.           final int totalLength = mTotalLength;    
  36.           mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));   
  37.           }   
  38.         //...  
  39. }  

源码中已经标注了一些注释,需要注意的是在每次对child测量完毕后,都会调用child.getMeasuredHeight()获取该子视图最终的高度,并将这个高度添加到mTotalLength中。但是getMeasuredHeight暂时避开了lp.weight>0的子View,因为后面会将把剩余高度按weight分配给相应的子View。因此可以得出以下结论:

1)如果我们在LinearLayout不使用weight属性,将只进行一次measure的过程

2)如果使用了weight属性,LinearLayout在第一次测量时避开设置过weight属性的子View,之后再对它们做第二次measure。由此可见,weight属性对性能是有影响的


3.   总结论

1RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而后者只需一次,但是有weight属性存在时,后者同样会进行两次measure

2RelativeLayout子View如果高度和RelativeLayout不同,会引发效率问题,可以使用padding代替margin以优化此问题。

3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout


结论中的第三条也解释了文章前言中的问题:DecorView的层级深度已知且固定的,上面一个标题栏,下面一个内容栏,采用RelativeLayout并不会降低层级深度,因此这种情况下使用LinearLayout效率更高。

而为开发者默认新建RelativeLayout是希望开发者能采用尽量少的View层级,很多效果是需要多层LinearLayout的嵌套,这必然不如一层的RelativeLayout性能更好。因此我们应该尽量减少布局嵌套,减少层级结构使用比如viewStubinclude等技巧。可以进行较大的布局优化。具体技巧的使用后面会继续写文总结。


最后希望大家多多点赞支持~

0 0
原创粉丝点击