LinearLayout和RelativeLayout性能区别

linearlayout和RelativeLayout在都属于viewgroup,view的绘制过程分为三部分:测量 measure,布局 layout , 绘制 draw
他们的在布局和绘制方面比较接近,在测量方面RelativeLayout比linearlayout慢。
为什么?源码分析

1.RelativeLayout的onMeasure分析

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
//...  
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();  
         applyHorizontalSizeRules(params, myWidth);  
         measureChildHorizontal(child, params, myWidth, myHeight);  
         if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {  
               offsetHorizontalAxis = true;  
         }  
    }  
}  
  
views = mSortedVerticalChildren;  
count = views.length;  
for (int i = 0; i < count; i++) {  
     View child = views[i];  
     if (child.getVisibility() != GONE) {  
           LayoutParams params = (LayoutParams) child.getLayoutParams();  
           applyVerticalSizeRules(params, myHeight);  
           measureChild(child, params, myWidth, myHeight);  
           if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {  
                 offsetVerticalAxis = true;  
           }  
           if (isWrapContentWidth) {  
                 width = Math.max(width, params.mRight);  
           }  
           if (isWrapContentHeight) {  
                 height = Math.max(height, params.mBottom);  
           }  
           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);  
           }  
       }  
  }  
  //...  
}

RelativeLayout:在横向和纵向都计算了一遍。为什么?
首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和Xml布局中View的顺序不同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许ViewB在横向上依赖ViewA,ViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。
同时需要注意的是View.measure()方法存在以下优化:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}

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

2 LinearLayout的onMeasure过程

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  if (mOrientation == VERTICAL) {    
    measureVertical(widthMeasureSpec, heightMeasureSpec);    
  } else {    
    measureHorizontal(widthMeasureSpec, heightMeasureSpec);    
  }    
}    
//LinearLayout会先做一个简单横纵方向判断,我们选择纵向这种情况继续分析  
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {  
//...  
for (int i = 0; i < count; ++i) {    
      final View child = getVirtualChildAt(i);    
      //... child为空、Gone以及分界线的情况略去  
     //累计权重  
      LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();    
      totalWeight += lp.weight;    
      //计算  
      if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {    
            //精确模式的情况下,子控件layout_height=0dp且weight大于0无法计算子控件的高度  
            //但是可以先把margin值合入到总值中,后面根据剩余空间及权值再重新计算对应的高度  
            final int totalLength = mTotalLength;    
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);    
      } else {    
           if (lp.height == 0 && lp.weight > 0) {    
            //如果这个条件成立,就代表 heightMode不是精确测量以及wrap_conent模式  
            //也就是说布局是越小越好,你还想利用权值多分剩余空间是不可能的,只设为wrap_content模式  
                 lp.height = LayoutParams.WRAP_CONTENT;    
           }    
    
          // 子控件测量  
          measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);           
          //获取该子视图最终的高度,并将这个高度添加到mTotalLength中  
          final int childHeight = child.getMeasuredHeight();    
          final int totalLength = mTotalLength;    
          mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));   
          }   
        //...  
}

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

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

3. 总结论

  • (1)RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而LinearLayout只需一次,但是有weight属性存在时,LinearLayout也需要两次measure。
  • (2)RelativeLayout的子View如果高度和RelativeLayout不同,会导致RelativeLayout在onMeasure()方法中做横向测量时,纵向的测量结果尚未完成,只好暂时使用自己的高度传入子View系统。而父View给子View传入的值也没有变化就不会做无谓的测量的优化会失效,解决办法就是可以使用padding代替margin以优化此问题。
  • (3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。

DecorView的层级深度已知且固定的,上面一个标题栏,下面一个内容栏,采用RelativeLayout并不会降低层级深度,因此这种情况下使用LinearLayout效率更高。
而为开发者默认新建RelativeLayout是希望开发者能采用尽量少的View层级,很多效果是需要多层LinearLayout的嵌套,这必然不如一层的RelativeLayout性能更好。因此我们应该尽量减少布局嵌套,减少层级结构,使用比如viewStub,include等技巧。可以进行较大的布局优化

如果层级多的使用RelativeLayout
如果层级固定的使用LinearLayout

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容