Android RelativeLayout和LinearLayout性能分析

来源:互联网 发布:win7软件中文乱码 编辑:程序博客网 时间:2024/05/20 20:47

       LinearLayout和RelativeLayout是Android中最常用的两个布局容器,在分析它们的性能之前,我们先来看一个问题。

       为什么使用Android Studio新建一个Blank Activity时默认的layout是RelativeLayout,而不是LinearLayout?

       我觉得这是基于性能的考虑,使用 LinearLayout 容易产生多层嵌套的布局,这会降低布局的性能。而RelativeLayout从使用上来讲,通常层级结构都比较扁平,使用LinearLayout的情况可以用一个RelativeLayout 来替换,以降低布局的层级。另外,RelativeLayout的使用要更灵活一些,作为根布局更容易满足各种情况。这应该就是Google在根布局中使用RelativeLayout的原因。

       这么看貌似RelativeLayout性能要更好一些,事实是否真的是这样呢?下面就仔细来分析下这个问题。

       我们知道一个View要绘制到屏幕上,会经历onMeasure、onLayout、onDraw三个阶段,要探讨它们的性能问题,就是比较这三个阶段的执行时间的长短。

       将几个TextView垂直摆放在屏幕上,分别使用LinearLayout和RelativeLayout,然后使用Hierarchy Viewer进行观察。

             

       从结果看,两种实现方式中onLayout、onDraw的执行时间基本一致,onMeasure的执行时间LinearLayout比RelativeLayout要短很多。

       为什么会出现这种现象呢?这就需要从LinearLayout和RelativeLayout的源码入手分析了。

       首先是RelativeLayout的源码

    @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;        }        mHasBaselineAlignedChild = false;        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++) {            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会在横向和纵向两个方向上分布,因此,需要在横向和纵向分别进行一次measure过程。

再看下LinearLayout的源码:

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

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {        mTotalLength = 0;        int maxWidth = 0;        int childState = 0;        int alternativeMaxWidth = 0;        int weightedMaxWidth = 0;        boolean allFillParent = true;        float totalWeight = 0;        final int count = getVirtualChildCount();                final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        boolean matchWidth = false;        boolean skippedMeasure = false;        final int baselineChildIndex = mBaselineAlignedChildIndex;                final boolean useLargestChild = mUseLargestChild;        int largestChildHeight = Integer.MIN_VALUE;        // See how tall everyone is. Also remember max width.        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);                skippedMeasure = true;            } 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);                }            }            /**             * If applicable, compute the additional offset to the child's baseline             * we'll need later when asked {@link #getBaseline}.             */            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {               mBaselineChildTop = mTotalLength;            }            // if we are trying to use a child index for our baseline, the above            // book keeping only works if there are no children above it with            // weight.  fail fast to aid the developer.            if (i < baselineChildIndex && lp.weight > 0) {                throw new RuntimeException("A child of LinearLayout with index "                        + "less than mBaselineAlignedChildIndex has weight > 0, which "                        + "won't work.  Either remove the weight, or don't set "                        + "mBaselineAlignedChildIndex.");            }            boolean matchWidthLocally = false;            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {                // The width of the linear layout will scale, and at least one                // child said it wanted to match our width. Set a flag                // indicating that we need to remeasure at least that view when                // we know our width.                matchWidth = true;                matchWidthLocally = true;            }            final int margin = lp.leftMargin + lp.rightMargin;            final int measuredWidth = child.getMeasuredWidth() + margin;            maxWidth = Math.max(maxWidth, measuredWidth);            childState = combineMeasuredStates(childState, child.getMeasuredState());            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;            if (lp.weight > 0) {                /*                 * Widths of weighted Views are bogus if we end up                 * remeasuring, so keep them separate.                 */                weightedMaxWidth = Math.max(weightedMaxWidth,                        matchWidthLocally ? margin : measuredWidth);            } else {                alternativeMaxWidth = Math.max(alternativeMaxWidth,                        matchWidthLocally ? margin : measuredWidth);            }            i += getChildrenSkipCount(child, i);        }        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {            mTotalLength += mDividerHeight;        }        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;                }                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)                        child.getLayoutParams();                // Account for negative margins                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));            }        }        // Add in our padding        mTotalLength += mPaddingTop + mPaddingBottom;        int heightSize = mTotalLength;        // Check against our minimum height        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());                // Reconcile our calculated size with the heightMeasureSpec        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;                // Either expand children with weight to take up available space or        // shrink them if they extend beyond our current bounds. If we skipped        // measurement on any children, we need to measure them now.        int delta = heightSize - mTotalLength;        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;            mTotalLength = 0;            for (int i = 0; i < count; ++i) {                final View child = getVirtualChildAt(i);                                if (child.getVisibility() == View.GONE) {                    continue;                }                                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();                                float childExtra = lp.weight;                if (childExtra > 0) {                    // Child said it could absorb extra space -- give him his share                    int share = (int) (childExtra * delta / weightSum);                    weightSum -= childExtra;                    delta -= share;                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            mPaddingLeft + mPaddingRight +                                    lp.leftMargin + lp.rightMargin, lp.width);                    // TODO: Use a field like lp.isMeasured to figure out if this                    // child has been previously measured                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {                        // child was measured once already above...                        // base new measurement on stored values                        int childHeight = child.getMeasuredHeight() + share;                        if (childHeight < 0) {                            childHeight = 0;                        }                                                child.measure(childWidthMeasureSpec,                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));                    } else {                        // child was skipped in the loop above.                        // Measure for this first time here                              child.measure(childWidthMeasureSpec,                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,                                        MeasureSpec.EXACTLY));                    }                    // Child may now not fit in vertical dimension.                    childState = combineMeasuredStates(childState, child.getMeasuredState()                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));                }                final int margin =  lp.leftMargin + lp.rightMargin;                final int measuredWidth = child.getMeasuredWidth() + margin;                maxWidth = Math.max(maxWidth, measuredWidth);                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&                        lp.width == LayoutParams.MATCH_PARENT;                alternativeMaxWidth = Math.max(alternativeMaxWidth,                        matchWidthLocally ? margin : measuredWidth);                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));            }            // Add in our padding            mTotalLength += mPaddingTop + mPaddingBottom;            // TODO: Should we recompute the heightSpec based on the new total length?        } else {            alternativeMaxWidth = Math.max(alternativeMaxWidth,                                           weightedMaxWidth);            // We have no limit, so make all weighted views as tall as the largest child.            // Children will have already been measured once.            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {                for (int i = 0; i < count; i++) {                    final View child = getVirtualChildAt(i);                    if (child == null || child.getVisibility() == View.GONE) {                        continue;                    }                    final LinearLayout.LayoutParams lp =                            (LinearLayout.LayoutParams) child.getLayoutParams();                    float childExtra = lp.weight;                    if (childExtra > 0) {                        child.measure(                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),                                        MeasureSpec.EXACTLY),                                MeasureSpec.makeMeasureSpec(largestChildHeight,                                        MeasureSpec.EXACTLY));                    }                }            }        }        ......    }
       我们可以看到,LinearLayout只进行横向或者纵向的measure,因此measure的时间要比RelativeLayout短,这也就印证了之前我们观察到的结果。但是,如果LinearLayout设置了weight属性,就有些不同了。如果使用weight属性,LinearLayout会避开设置过weight属性的view做一次measure,然后再对设置过weight属性的view做第二次measure。也就是说,设置了weight属性的LinearLayout的绘制效率比没有设置的要差。

       总结一下上面分析的结论:

1.RelativeLayout会对子View进行两次measure,LinearLayout只对子View进行一次measure,而在设置了weight时,也会对weight进行两次measure,通常情况下,LinearLayout的性能要优于RelativeLayout。

2.在View的层级扁平,没有过多的嵌套的情况下,用LinearLayout效率更高,并且要尽量减少使用weight属性。

3.如果View的层级嵌套过多,则需要使用RelativeLayout来降低层级,因为Android是递归生成View的,过多的层级嵌套会严重影响View的绘制效率。


       参考文章:http://www.devdiv.com/forum.php?mod=viewthread&tid=197235

http://www.cnblogs.com/hellsong/p/4613426.html?utm_source=tuicool&utm_medium=referral


2 0
原创粉丝点击