LinearLayout和RelativeLayout性能对比,你认为谁的效率更高?

640?wx_fmt=gif


温馨提示

请滑动到文章末尾,长按识别「抽奖」小程序,每日现金红包,月月百元大红包,「加蛋添肉」快到碗里来。


前言


好多年没有被淋成落汤鸡」了,今天的雨是真的很大,毫不夸张的说,我鞋子里至少装了一斤的雨水。厦门的天气暴雨说来就来,让你猝不及防。


今天的主题是 LinearLayout 和 RelativeLayout 性能对比,你认为谁的效率更高?曾在百度的面试中被问到过。


正文


我们先来做个简单的测试,分别用 LinearLayout 和 RelativeLayout 布局下图样式,打印时长 MeasureLayoutdraw 时长。


640?wx_fmt=png


结论如下:

 
 
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 慢了一大截。


接下来分析 RelativeLayout 与 LinearLayout 的 Measure 源码。


RelativeLayout的onMeasure相关源码:

 
 
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();	
        int[] rules = params.getRules(layoutDirection);	
	
        applyHorizontalSizeRules(params, myWidth, rules);	
        measureChildHorizontal(child, params, myWidth, myHeight);	
	
        if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {	
          offsetHorizontalAxis = true;	
        }	
      }	
    }	
	
    views = mSortedVerticalChildren;	
    count = views.length;	
    final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;	
	
    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) {	
          if (isLayoutRtl()) {	
            if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {	
              width = Math.max(width, myWidth - params.mLeft);	
            } else {	
              width = Math.max(width, myWidth - params.mLeft - params.leftMargin);	
            }	
          } else {	
            if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {	
              width = Math.max(width, params.mRight);	
            } else {	
              width = Math.max(width, params.mRight + params.rightMargin);	
            }	
          }	
        }	
	
        if (isWrapContentHeight) {	
          if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {	
            height = Math.max(height, params.mBottom);	
          } else {	
            height = Math.max(height, params.mBottom + params.bottomMargin);	
          }	
        }	
	
        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 会对子 View 做两次 measure。这是为什么呢?


首先 RelativeLayout 中子 View 的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中 View 的顺序并不相同,在确定每个子 View 的位置的时候,就需要先给所有的子 View 排序一下。


又因为 RelativeLayout 允许 A,B 2 个子 View,横向上 B 依赖 A,纵向上 A 依赖 B。所以需要横向纵向分别进行一次排序测量。


LinearLayout的onMeasure相关源码:

 
 
  @Override	
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {	
    if (mOrientation == VERTICAL) {	
      measureVertical(widthMeasureSpec, heightMeasureSpec);	
    } else {	
      measureHorizontal(widthMeasureSpec, heightMeasureSpec);	
    }	
  }

与 RelativeLayout 相比 LinearLayout 的 measure 就简单明了的多了,先判断线性规则,然后执行对应方向上的测量。比如这样:

 
 
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);	
      } 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 0	
          oldHeight = 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);	
        }	
      }

父视图在对子视图进行 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会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure


  • RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。


  • 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。


最后再思考一下文章开头那个矛盾的问题,为什么 Google 给开发者默认新建了个 RelativeLayout,而自己却在 DecorView 中用了个LinearLayout。


因为 DecorView 的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用 RelativeLayout 并不会降低层级深度,所以此时在根节点上用 LinearLayout 是效率最高的。


而之所以给开发者默认新建了个 RelativeLayout 是希望开发者能采用尽量少的 View 层级来表达布局以实现性能最优,因为复杂的 View 嵌套对性能的影响会更大一些。


推荐阅读:

简历那些事?一份好工作得益于一份好简历,小新职场记

如何准备大厂技术面试?偷学「大师兄」秘籍!


640?wx_fmt=png长按识别小程序,参与抽奖

640?wx_fmt=png


更多现金红包,请长按二维码640?wx_fmt=other 640?wx_fmt=png

目前100000+人已关注加入我们

640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif

640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值