zoukankan      html  css  js  c++  java
  • 怎样优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析

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

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

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

    先看一些现象吧:用Android studio,新建一个Activity自己主动生成的布局文件都是RelativeLayout,也许你会觉得这是IDE的默认设置问题。事实上不然,这是由 android-sdk ools emplatesactivitiesEmptyActivity oot eslayoutactivity_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()方法
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mDirtyHierarchy) {
                mDirtyHierarchy = false;
                sortChildren();
            }
    
            int myWidth = -1;
            int myHeight = -1;
    
            int width = 0;
            int height = 0;
    
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            // Record our dimensions if they are known;
            if (widthMode != MeasureSpec.UNSPECIFIED) {
                myWidth = widthSize;
            }
    
            if (heightMode != MeasureSpec.UNSPECIFIED) {
                myHeight = heightSize;
            }
    
            if (widthMode == MeasureSpec.EXACTLY) {
                width = myWidth;
            }
    
            if (heightMode == MeasureSpec.EXACTLY) {
                height = myHeight;
            }
    
            View ignore = null;
            int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
            final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
            gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
    
            int left = Integer.MAX_VALUE;
            int top = Integer.MAX_VALUE;
            int right = Integer.MIN_VALUE;
            int bottom = Integer.MIN_VALUE;
    
            boolean offsetHorizontalAxis = false;
            boolean offsetVerticalAxis = false;
    
            if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
                ignore = findViewById(mIgnoreGravity);
            }
    
            final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
            final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
    
            // We need to know our size for doing the correct computation of children positioning in RTL
            // mode but there is no practical way to get it instead of running the code below.
            // So, instead of running the code twice, we just set the width to a "default display width"
            // before the computation and then, as a last pass, we will update their real position with
            // an offset equals to "DEFAULT_WIDTH - width".
            final int layoutDirection = getLayoutDirection();
            if (isLayoutRtl() && myWidth == -1) {
                myWidth = DEFAULT_WIDTH;
            }
    
            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++) {
                final View child = views[i];
                if (child.getVisibility() != GONE) {
                    final LayoutParams params = (LayoutParams) child.getLayoutParams();
    
                    applyVerticalSizeRules(params, myHeight, child.getBaseline());
                    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);
                    }
                }
            }
    
            // Use the top-start-most laid out view as the baseline. RTL offsets are
            // applied later, so we can use the left-most edge as the starting edge.
            View baselineView = null;
            LayoutParams baselineParams = null;
            for (int i = 0; i < count; i++) {
                final View child = views[i];
                if (child.getVisibility() != GONE) {
                    final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
                    if (baselineView == null || baselineParams == null
                            || compareLayoutPosition(childParams, baselineParams) < 0) {
                        baselineView = child;
                        baselineParams = childParams;
                    }
                }
            }
            mBaselineView = baselineView;
    
            if (isWrapContentWidth) {
                // Width already has left padding in it since it was calculated by looking at
                // the right of each child view
                width += mPaddingRight;
    
                if (mLayoutParams != null && mLayoutParams.width >= 0) {
                    width = Math.max(width, mLayoutParams.width);
                }
    
                width = Math.max(width, getSuggestedMinimumWidth());
                width = resolveSize(width, widthMeasureSpec);
    
                if (offsetHorizontalAxis) {
                    for (int i = 0; i < count; i++) {
                        final View child = views[i];
                        if (child.getVisibility() != GONE) {
                            final LayoutParams params = (LayoutParams) child.getLayoutParams();
                            final int[] rules = params.getRules(layoutDirection);
                            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                                centerHorizontal(child, params, width);
                            } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
                                final int childWidth = child.getMeasuredWidth();
                                params.mLeft = width - mPaddingRight - childWidth;
                                params.mRight = params.mLeft + childWidth;
                            }
                        }
                    }
                }
            }
    
            if (isWrapContentHeight) {
                // Height already has top padding in it since it was calculated by looking at
                // the bottom of each child view
                height += mPaddingBottom;
    
                if (mLayoutParams != null && mLayoutParams.height >= 0) {
                    height = Math.max(height, mLayoutParams.height);
                }
    
                height = Math.max(height, getSuggestedMinimumHeight());
                height = resolveSize(height, heightMeasureSpec);
    
                if (offsetVerticalAxis) {
                    for (int i = 0; i < count; i++) {
                        final View child = views[i];
                        if (child.getVisibility() != GONE) {
                            final LayoutParams params = (LayoutParams) child.getLayoutParams();
                            final int[] rules = params.getRules(layoutDirection);
                            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
                                centerVertical(child, params, height);
                            } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
                                final int childHeight = child.getMeasuredHeight();
                                params.mTop = height - mPaddingBottom - childHeight;
                                params.mBottom = params.mTop + childHeight;
                            }
                        }
                    }
                }
            }
    
            if (horizontalGravity || verticalGravity) {
                final Rect selfBounds = mSelfBounds;
                selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
                        height - mPaddingBottom);
    
                final Rect contentBounds = mContentBounds;
                Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
                        layoutDirection);
    
                final int horizontalOffset = contentBounds.left - left;
                final int verticalOffset = contentBounds.top - top;
                if (horizontalOffset != 0 || verticalOffset != 0) {
                    for (int i = 0; i < count; i++) {
                        final View child = views[i];
                        if (child.getVisibility() != GONE && child != ignore) {
                            final LayoutParams params = (LayoutParams) child.getLayoutParams();
                            if (horizontalGravity) {
                                params.mLeft += horizontalOffset;
                                params.mRight += horizontalOffset;
                            }
                            if (verticalGravity) {
                                params.mTop += verticalOffset;
                                params.mBottom += verticalOffset;
                            }
                        }
                    }
                }
            }
    
            if (isLayoutRtl()) {
                final int offsetWidth = myWidth - width;
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        params.mLeft -= offsetWidth;
                        params.mRight -= offsetWidth;
                    }
                }
            }
    
            setMeasuredDimension(width, height);
        }


    依据源代码我们发现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属性对性能是有影响的,并且本身有大坑,请注意避让。

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

    小结

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

    RelativeLayout还有一个性能问题

    对照到这里就结束了嘛?显然没有!

    我们再看看View的Measure()方法都干了些什么?

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
                         ......
          }
           mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
      }


    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的源代码。做了什么?

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int count = getChildCount();
            final boolean measureMatchParentChildren =
                    MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
            //当FrameLayout的宽和高,仅仅有同一时候设置为match_parent或者指定的size,那么这个
     
            //measureMatchParentChlidren = false,否则为true。以下会用到这个变量
             
            mMatchParentChildren.clear();
            int maxHeight = 0;     
            int maxWidth = 0;
            int childState = 0;    //宽高的期望类型
     
            for (int i = 0; i < count; i++) {    //一次遍历每个不为GONE的子view
         
                final View child = getChildAt(i);    
                if (mMeasureAllChildren || child.getVisibility() != GONE) {
                    //去掉FrameLayout的左右padding。子view的左右margin,这时候,再去
     
                    //计算子view的期望的值
                     
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                     
                     
                    /*maxWidth找到子View中最大的宽。高同理,为什么要找到他,由于在这里,FrameLayout是wrap
                    -content.他的宽高肯定受子view的影响*/
                     
                    maxWidth = Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                    maxHeight = Math.max(maxHeight,
                            child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                    childState = combineMeasuredStates(childState, child.getMeasuredState());
                    
                    /*以下的推断,仅仅有上面的FragLayout的width和height都设置为match_parent 才不会运行
                    此处的mMatchParentChlidren的list里存的是设置为match_parent的子view。
                    结合上面两句话的意思,当FrameLayout设置为wrap_content。这时候要把全部宽高设置为
                    match_parent的子View都记录下来,记录下来干什么呢?
                    这时候FrameLayout的宽高同一时候受子View的影响*/
                        
                     if (measureMatchParentChildren) {
                        if (lp.width == LayoutParams.MATCH_PARENT ||
                                lp.height == LayoutParams.MATCH_PARENT) {
                            mMatchParentChildren.add(child);
                        }
                    }
                }
            }
             
            // Account for padding too
            maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
            maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
             
            // Check against our minimum height and width
            maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
            // Check against our foreground's minimum height and width
            final Drawable drawable = getForeground();
            if (drawable != null) {
                maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
                maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
            }
     
            //设置測量过的宽高
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT));
            count = mMatchParentChildren.size();//这个大小就是子view中设定为match_parent的个数
     
            if (count > 1) {
                for (int i = 0; i < count; i++) {
                    //这里看上去又一次计算了一遍
     
                    final View child = mMatchParentChildren.get(i);
                    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                    int childWidthMeasureSpec;
                    int childHeightMeasureSpec;
                    /*假设子view的宽是match_parent,则宽度期望值是总宽度-padding-margin
                     假设子view的宽是指定的比方100dp。则宽度期望值是padding+margin+width
                     这个非常easy理解,以下的高同理*/
                    if (lp.width == LayoutParams.MATCH_PARENT) {
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                                getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
                                lp.leftMargin - lp.rightMargin,
                                MeasureSpec.EXACTLY);
                    } else {
                        childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                                lp.leftMargin + lp.rightMargin,
                                lp.width);
                    }
                     
                    if (lp.height == LayoutParams.MATCH_PARENT) {
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                                getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
                                lp.topMargin - lp.bottomMargin,
                                MeasureSpec.EXACTLY);
                    } else {
                        childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                                getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                                lp.topMargin + lp.bottomMargin,
                                lp.height);
                    }
                    //把这部分子view又一次计算大小
     
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }

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

     if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
    
                mTotalLength = 0;
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
                    if (child == null) {
                        mTotalLength += measureNullChild(i);
                        continue;
                    }
                    if (child.getVisibility() == GONE) {
                        i += getChildrenSkipCount(child, i);
                        continue;
                    }
    }

    由于二级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等一些技巧。

    第一时间获得博客更新提醒,以及很多其它android干货,源代码分析。欢迎关注我的微信公众号,扫一扫下方二维码,就可以关注。 
    这里写图片描写叙述

  • 相关阅读:
    你没听过的乔布斯语录:苹果如果没这么独,可能更成功
    关于Apache mod_rewrite的中文配置、使用和语法介绍(实现URL重写和防盗链功能)
    与调试器共舞
    libxml2.dylb 导致<libxml/tree.h> 老是找不到头文件
    国家气象局提供的天气预报接口(完整Json接口)
    dSYM 文件分析工具
    总结
    1-22
    【图论】
    好像要开始图论了....但搜索还剩了点....
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7288937.html
Copyright © 2011-2022 走看看