如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析

来源:互联网 发布:linux挂载光盘 编辑:程序博客网 时间:2024/05/19 21:16

转载自:http://blog.csdn.net/hejjunlin/article/details/51159419

转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/51159419

如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析

工作一段时间后,经常会被领导说,你这个进入速度太慢了,竞品的进入速度很快,你搞下优化吧?每当这时,你会怎么办?功能实现都有啊,进入时要加载那么多view,这也没办法啊,等等。

先看一些现象吧:用Android studio,新建一个Activity自动生成的布局文件都是RelativeLayout,或许你会认为这是IDE的默认设置问题,其实不然,这是由 android-sdk\tools\templates\activities\EmptyActivity\root\res\layout\activity_simple.xml.ftl 这个文件事先就定好了的,也就是说这是Google的选择,而非IDE的选择。那SDK为什么会默认给开发者新建一个默认的RelativeLayout布局呢?当然是因为RelativeLayout的性能更优,性能至上嘛。但是我们再看看默认新建的这个RelativeLayout的父容器,也就是当前窗口的顶级View——DecorView,它却是个垂直方向的LinearLayout,上面是标题栏,下面是内容栏。那么问题来了,Google为什么给开发者默认新建了个RelativeLayout,而自己却偷偷用了个LinearLayout,到底谁的性能更高,开发者该怎么选择呢?

View的一些基本工作原理

先通过几个问题,简单的了解写android中View的工作原理吧。

View是什么?

简单来说,View是Android系统在屏幕上的视觉呈现,也就是说你在手机屏幕上看到的东西都是View。

View是怎么绘制出来的?

View的绘制流程是从ViewRoot的performTraversals()方法开始,依次经过measure(),layout()和draw()三个过程才最终将一个View绘制出来。

View是怎么呈现在界面上的?

Android中的视图都是通过Window来呈现的,不管Activity、Dialog还是Toast它们都有一个Window,然后通过WindowManager来管理View。Window和顶级View——DecorView的通信是依赖ViewRoot完成的。

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.762ms
Layout:0.167ms
draw:7.665ms

RelativeLayout

Measure:2.180ms
Layout:0.156ms
draw:7.694ms
从这个数据来看无论使用RelativeLayout还是LinearLayout,layout和draw的过程两者相差无几,考虑到误差的问题,几乎可以认为两者不分伯仲,关键是Measure的过程RelativeLayout却比LinearLayout慢了一大截。

Measure都干什么了

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


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

LinearLayout的onMeasure()方法
[java] view plain copy
 print?
  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就简单明了的多了,先判断线性规则,然后执行对应方向上的测量。随便看一个吧。

[java] view plain copy
 print?
  1. for (int i = 0; i < count; ++i) {  
  2.       final View child = getVirtualChildAt(i);  
  3.   
  4.       if (child == null) {  
  5.         mTotalLength += measureNullChild(i);  
  6.         continue;  
  7.       }  
  8.   
  9.       if (child.getVisibility() == View.GONE) {  
  10.        i += getChildrenSkipCount(child, i);  
  11.        continue;  
  12.       }  
  13.   
  14.       if (hasDividerBeforeChildAt(i)) {  
  15.         mTotalLength += mDividerHeight;  
  16.       }  
  17.   
  18.       LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
  19.   
  20.       totalWeight += lp.weight;  
  21.   
  22.       if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
  23.         // Optimization: don't bother measuring children who are going to use  
  24.         // leftover space. These views will get measured again down below if  
  25.         // there is any leftover space.  
  26.         final int totalLength = mTotalLength;  
  27.         mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);  
  28.       } else {  
  29.         int oldHeight = Integer.MIN_VALUE;  
  30.   
  31.         if (lp.height == 0 && lp.weight > 0) {  
  32.           // heightMode is either UNSPECIFIED or AT_MOST, and this  
  33.           // child wanted to stretch to fill available space.  
  34.           // Translate that to WRAP_CONTENT so that it does not end up  
  35.           // with a height of 0  
  36.           oldHeight = 0;  
  37.           lp.height = LayoutParams.WRAP_CONTENT;  
  38.         }  
  39.   
  40.         // Determine how big this child would like to be. If this or  
  41.         // previous children have given a weight, then we allow it to  
  42.         // use all available space (and we will shrink things later  
  43.         // if needed).  
  44.         measureChildBeforeLayout(  
  45.            child, i, widthMeasureSpec, 0, heightMeasureSpec,  
  46.            totalWeight == 0 ? mTotalLength : 0);  
  47.   
  48.         if (oldHeight != Integer.MIN_VALUE) {  
  49.          lp.height = oldHeight;  
  50.         }  
  51.   
  52.         final int childHeight = child.getMeasuredHeight();  
  53.         final int totalLength = mTotalLength;  
  54.         mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +  
  55.            lp.bottomMargin + getNextLocationOffset(child));  
  56.   
  57.         if (useLargestChild) {  
  58.           largestChildHeight = Math.max(childHeight, largestChildHeight);  
  59.         }  
  60.       }  

父视图在对子视图进行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属性对性能是有影响的,而且本身有大坑,请注意避让。

本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/51159419

小结

从源码中我们似乎能看出,我们先前的测试结果中RelativeLayout不如LinearLayout快的根本原因是RelativeLayout需要对其子View进行两次measure过程。而LinearLayout则只需一次measure过程,所以显然会快于RelativeLayout,但是如果LinearLayout中有weight属性,则也需要进行两次measure,但即便如此,应该仍然会比RelativeLayout的情况好一点。

RelativeLayout另一个性能问题

对比到这里就结束了嘛?显然没有!我们再看看View的Measure()方法都干了些什么?

[java] view plain copy
 print?
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.   
  3.     if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||  
  4.         widthMeasureSpec != mOldWidthMeasureSpec ||  
  5.         heightMeasureSpec != mOldHeightMeasureSpec) {  
  6.                      ......  
  7.       }  
  8.        mOldWidthMeasureSpec = widthMeasureSpec;  
  9.     mOldHeightMeasureSpec = heightMeasureSpec;  
  10.   
  11.     mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |  
  12.         (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension  
  13.   }  


View的measure方法里对绘制过程做了一个优化,如果我们或者我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure。但是上面已经说了RelativeLayout要做两次measure,而在做横向的测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统,假如子View的Height不等于(设置了margin)myHeight的高度,那么measure中上面代码所做得优化将不起作用,这一过程将进一步影响RelativeLayout的绘制性能。而LinearLayout则无这方面的担忧。解决这个问题也很好办,如果可以,尽量使用padding代替margin。

FrameLayout和LinearLayout性能PK



FrameLayout

LinearLayout

Measure:2.058ms
Layout:0.296ms
draw:3.857ms

FrameLayout

Measure:1.334ms
Layout:0.213ms
draw:3.680ms
从这个数据来使用LinearLayout,仅嵌套一个LinearLayou,在onMeasure就相关2倍时间和FrameLayout相比,layout和draw的过程两者相差无几,考虑到误差的问题,几乎可以认为两者不分伯仲

看下FrameLayout的源码,做了什么?

[java] view plain copy
 print?
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         int count = getChildCount();  
  3.         final boolean measureMatchParentChildren =  
  4.                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||  
  5.                 MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;  
  6.         //当FrameLayout的宽和高,只有同时设置为match_parent或者指定的size,那么这个  
  7.    
  8.         //measureMatchParentChlidren = false,否则为true。下面会用到这个变量  
  9.            
  10.         mMatchParentChildren.clear();  
  11.         int maxHeight = 0;       
  12.         int maxWidth = 0;  
  13.         int childState = 0;    //宽高的期望类型  
  14.    
  15.         for (int i = 0; i < count; i++) {    //一次遍历每一个不为GONE的子view  
  16.        
  17.             final View child = getChildAt(i);      
  18.             if (mMeasureAllChildren || child.getVisibility() != GONE) {  
  19.                 //去掉FrameLayout的左右padding,子view的左右margin,这时候,再去  
  20.    
  21.                 //计算子view的期望的值  
  22.                    
  23.                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);  
  24.                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();  
  25.                    
  26.                    
  27.                 /*maxWidth找到子View中最大的宽,高同理,为什么要找到他,因为在这里,FrameLayout是wrap 
  28.                 -content.他的宽高肯定受子view的影响*/  
  29.                    
  30.                 maxWidth = Math.max(maxWidth,  
  31.                         child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);  
  32.                 maxHeight = Math.max(maxHeight,  
  33.                         child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
  34.                 childState = combineMeasuredStates(childState, child.getMeasuredState());  
  35.                   
  36.                 /*下面的判断,只有上面的FragLayout的width和height都设置为match_parent 才不会执行 
  37.                 此处的mMatchParentChlidren的list里存的是设置为match_parent的子view。 
  38.                 结合上面两句话的意思,当FrameLayout设置为wrap_content,这时候要把所有宽高设置为 
  39.                 match_parent的子View都记录下来,记录下来干什么呢? 
  40.                 这时候FrameLayout的宽高同时受子View的影响*/  
  41.                       
  42.                  if (measureMatchParentChildren) {  
  43.                     if (lp.width == LayoutParams.MATCH_PARENT ||  
  44.                             lp.height == LayoutParams.MATCH_PARENT) {  
  45.                         mMatchParentChildren.add(child);  
  46.                     }  
  47.                 }  
  48.             }  
  49.         }  
  50.            
  51.         // Account for padding too  
  52.         maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();  
  53.         maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();  
  54.            
  55.         // Check against our minimum height and width  
  56.         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());  
  57.         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());  
  58.         // Check against our foreground's minimum height and width  
  59.         final Drawable drawable = getForeground();  
  60.         if (drawable != null) {  
  61.             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());  
  62.             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());  
  63.         }  
  64.    
  65.         //设置测量过的宽高  
  66.         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),  
  67.                 resolveSizeAndState(maxHeight, heightMeasureSpec,  
  68.                         childState << MEASURED_HEIGHT_STATE_SHIFT));  
  69.         count = mMatchParentChildren.size();//这个大小就是子view中设定为match_parent的个数  
  70.    
  71.         if (count > 1) {  
  72.             for (int i = 0; i < count; i++) {  
  73.                 //这里看上去重新计算了一遍  
  74.    
  75.                 final View child = mMatchParentChildren.get(i);  
  76.                 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  
  77.                 int childWidthMeasureSpec;  
  78.                 int childHeightMeasureSpec;  
  79.                 /*如果子view的宽是match_parent,则宽度期望值是总宽度-padding-margin 
  80.                  如果子view的宽是指定的比如100dp,则宽度期望值是padding+margin+width 
  81.                  这个很容易理解,下面的高同理*/  
  82.                 if (lp.width == LayoutParams.MATCH_PARENT) {  
  83.                     childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -  
  84.                             getPaddingLeftWithForeground() - getPaddingRightWithForeground() -  
  85.                             lp.leftMargin - lp.rightMargin,  
  86.                             MeasureSpec.EXACTLY);  
  87.                 } else {  
  88.                     childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,  
  89.                             getPaddingLeftWithForeground() + getPaddingRightWithForeground() +  
  90.                             lp.leftMargin + lp.rightMargin,  
  91.                             lp.width);  
  92.                 }  
  93.                    
  94.                 if (lp.height == LayoutParams.MATCH_PARENT) {  
  95.                     childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -  
  96.                             getPaddingTopWithForeground() - getPaddingBottomWithForeground() -  
  97.                             lp.topMargin - lp.bottomMargin,  
  98.                             MeasureSpec.EXACTLY);  
  99.                 } else {  
  100.                     childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,  
  101.                             getPaddingTopWithForeground() + getPaddingBottomWithForeground() +  
  102.                             lp.topMargin + lp.bottomMargin,  
  103.                             lp.height);  
  104.                 }  
  105.                 //把这部分子view重新计算大小  
  106.    
  107.                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  108.             }  
  109.         }  
  110.     }  

本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/51159419
加了一个嵌套,onMeasure时间,多了将近一倍,原因在于:LinearLayout在某一方向onMeasure,发现还存在LinearLayout。将触发 

[java] view plain copy
 print?
  1.  if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {  
  2.   
  3.             mTotalLength = 0;  
  4.             for (int i = 0; i < count; ++i) {  
  5.                 final View child = getVirtualChildAt(i);  
  6.                 if (child == null) {  
  7.                     mTotalLength += measureNullChild(i);  
  8.                     continue;  
  9.                 }  
  10.                 if (child.getVisibility() == GONE) {  
  11.                     i += getChildrenSkipCount(child, i);  
  12.                     continue;  
  13.                 }  
  14. }  

因为二级LinearLayout父类是Match_parent,所以就存在再层遍历。在时间就自然存在消耗。

结论

1.RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
2.RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
3.在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
最后再思考一下文章开头那个矛盾的问题,为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

4.能用两层LinearLayout,尽量用一个RelativeLayout,在时间上此时RelativeLayout耗时更小。另外LinearLayout慎用layout_weight,也将会增加一倍耗时操作。由于使用LinearLayout的layout_weight,大多数时间是不一样的,这会降低测量的速度。这只是一个如何合理使用Layout的案例,必要的时候,你要小心考虑是否用layout weight。总之减少层级结构,才是王道,让onMeasure做延迟加载,用viewStub,include等一些技巧。


阅读全文
0 0
原创粉丝点击