看到几篇关于RelativeLayout和LinearLayout性能分析的博客,写的相当不错,这里在大神的基础上,增加了
部分内容
RelativeLayout和LinearLayout是Android中常用的布局,两者的使用会极大的影响程序生成每一帧的性能,
因此,正确的使用它们是提升程序性能的重要工作。记得以前,较低的SDK版本新建Android项目时,默认的布局文件
是采用线性布局LinearLayout,但现在自动生成的布局文件都是RelativeLayout,或许你会认为这是IDE的默认设置
问题,其实不然,这由 android-sdk\tools\templates\activities\BlankActivity\root\res\layout\
activity_simple.xml.ftl 这个文件事先就定好了的,也就是说这是Google的选择,而非IDE的选择。
那SDK为什么会默认给开发者新建一个默认的RelativeLayout布局呢?<-----原因见最后小结
当然是因为RelativeLayout的性能更优,性能至上嘛。但是我们再看看默认新建的这个RelativeLayout的
父容器,也就是当前窗口的顶级View——DecorView,它却是个垂直方向的LinearLayout,上面是标题栏,
下面是内容栏。那么问题来了,Google为什么给开发者默认新建了个RelativeLayout,而自己却偷偷用了
个LinearLayout,到底谁的性能更高,开发者该怎么选择呢?
下面将通过分析它们的源码来探讨其View绘制性能,并得出其正确的使用方法。
一、View的一些基本工作原理
先通过几个问题,简单的了解写android中View的工作原理吧。
(1)View是什么?
简单来说,View是Android系统在屏幕上的视觉呈现,也就是说你在手机屏幕上看到的东西都是View。
(2)View是怎么绘制出来的?
View的绘制流程是从ViewRoot的performTraversals()方法开始,依次经过measure(),layout()和
draw()三个过程才最终将一个View绘制出来。
(3)View是怎么呈现在界面上的?
Android中的视图都是通过Window来呈现的,不管Activity、Dialog还是Toast它们都有一个Window,然后
通过WindowManager来管理View。Window和顶级View——DecorView的通信是依赖ViewRoot完成的。
(4)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.738ms
Layout:0.176ms
draw:7.655ms
RelativeLayout
Measure:2.280ms
Layout:0.153ms
draw:7.696ms
从这个数据来看无论使用RelativeLayout还是LinearLayout,layout和draw的过程两者相差无几,
考虑到误差的问题,几乎可以认为两者不分伯仲,关键是Measure的过程RelativeLayout却比LinearLayout慢了一
大截。
(1)RelativeLayout的onMeasure()方法
- 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);
- }
- }
- }
- if (mHasBaselineAlignedChild) {
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
- alignBaseline(child, params);
- 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);
- }
- }
- }
- }
- 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++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- 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++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- 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++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE && child != ignore) {
- 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++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- 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。所以需要横向纵向分别进行一次排序测量。 mSortedHorizontalChildren和
mSortedVerticalChildren是分别对水平方向的子控件和垂直方向的子控件进行排序后的View数组。
(2)LinearLayout的onMeasure()方法
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mOrientation == VERTICAL) {
- measureVertical(widthMeasureSpec, heightMeasureSpec);
- } else {
- measureHorizontal(widthMeasureSpec, heightMeasureSpec);
- }
- }
与RelativeLayout相比LinearLayout的measure就简单的多,只需判断线性布局是水平布局还是垂直布局即可,
然后才进行测量:
- /**
- * Measures the children when the orientation of this LinearLayout is set
- * to {@link #VERTICAL}.
- *
- * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
- * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
- *
- * @see #getOrientation()
- * @see #setOrientation(int)
- * @see #onMeasure(int, int)
- */
- 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));
- }
- }
- }
- }
- if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
- maxWidth = alternativeMaxWidth;
- }
- maxWidth += mPaddingLeft + mPaddingRight;
- // Check against our minimum width
- maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
- heightSizeAndState);
- if (matchWidth) {
- forceUniformWidth(count, heightMeasureSpec);
- }
- }
LinearLayout首先会对所有的子View进行measure,并计算totalWeight(所有子View的weight属性之和),然后判断
子View的weight属性是否为最大,如为最大则将剩余的空间分配给它。如果不使用weight属性进行布局,则不进行第
二次measure。
父视图在对子视图进行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不如LinearLayout快的根本原因是 RelativeLayout需要对其子View进行两次measure过程。而LinearLayout则只需一次measure过程,所以显然会快于 RelativeLayout,但是如果LinearLayout中有weight属性,则也需要进行两次measure,但即便如此,应该仍然会 比RelativeLayout的情况好一点。 RelativeLayout另一个性能问题 对比到这里就结束了嘛?显然没有!我们再看看View的Measure()方法都干了些什么?
<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><strong>[java]</strong>
<a target=_blank href="http://blog.csdn.net/guyuealian/article/details/52162774#" class="ViewSource"
title="view plain">view plain</a><span data-mod="popu_168"> <a target=_blank href="http://
blog.csdn.net/guyuealian/article/details/52162774#" class="CopyToClipboard" title="copy">copy</a>
</span><span data-mod="popu_169"> </span><span class="tracking-ad" data-mod="popu_167">
<a target=_blank href="https://code.csdn.net/snippets/1818639" target="_blank" title="在CODE上查看代码
片" style="text-indent:0;"><img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码
片" style="position:relative;top:1px;left:2px;" width="12" height="12" /></a></span>
<span class="tracking-ad" data-mod="popu_170"><a target=_blank href="
https://code.csdn.net/snippets/1818639/fork" target="_blank" title="派生到我的代码片"
style="text-indent:0;"><img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片"
style="position:relative;top:2px;left:2px;" width="12" height="12" /></a></span></div></div>
<ol class="dp-j" start="1"><li class="alt"><span><span class="keyword">public</span><span>
</span><span class="keyword">final</span><span> </span><span class="keyword">void</span><span>
measure(</span><span class="keyword">int</span><span> widthMeasureSpec, </span><span class="keyword">int</span><span> heightMeasureSpec) { </span></span></li><li><span> <span class="keyword">if</span><span> ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || </span></span></li><li class="alt"><span> widthMeasureSpec != mOldWidthMeasureSpec || </span></li><li><span> heightMeasureSpec != mOldHeightMeasureSpec) { </span></li><li class="alt"><span> ...... </span></li><li><span> } </span></li><li class="alt"><span> mOldWidthMeasureSpec = widthMeasureSpec; </span></li><li><span> mOldHeightMeasureSpec = heightMeasureSpec; </span></li><li class="alt"><span> </span></li><li><span> mMeasureCache.put(key, ((<span class="keyword">long</span><span>) mMeasuredWidth) << </span><span class="number">32</span><span> | </span></span></li><li class="alt"><span> (<span class="keyword">long</span><span>) mMeasuredHeight & 0xffffffffL); </span><span class="comment">// suppress sign extension</span><span> </span></span></li><li><span> } </span></li></ol></div>
View的measure方法里对绘制过程做了一个优化,如果我们或者我们的子View没有要求强制刷新,
而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure。但是上面已经
说了RelativeLayout要做两次measure,而在做横向的测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传
入子View系统,假如子View的Height不等于(设置了margin)myHeight的高度,那么measure中上面代码所做得优化
将不起作用,这一过程将进一步影响RelativeLayout的绘制性能。而LinearLayout则无这方面的担忧。解决这个问题
也很好办,如果可以,尽量使用padding代替margin。
结论
(1)RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View 2次onMeasure (2)RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题 会更加严重。如果可以,尽量使用padding代替margin。 (3)在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。 (4)提高绘制性能的使用方式 根据上面源码的分析,RelativeLayout将对所有的子View进行两次measure,而LinearLayout在使用weight属性 进行布局时也会对子View进行两次measure,如果他们位于整个View树的顶端时并可能进行多层的嵌套时,位于底层 的View将会进行大量的measure操作,大大降低程序性能。因此,应尽量将RelativeLayout和LinearLayout置于View 树的底层,并减少嵌套。
最后思考一下文章开头的疑问:较低的SDK版本新建Android项目时,默认的布局文件是采用线性布局 LinearLayout,但现在自动生成的布局文件都是RelativeLayout,为什么呢?
这是Google关于RelativeLayout的说明:
- A RelativeLayout is a very powerful utility for designing a user interface because
- it can eliminate nested view groups and keep your layout hierarchy flat, which
- improves performance. If you find yourself using several nested LinearLayout groups,
- you may be able to replace them with a single RelativeLayout.
Google的意思是“性能至上”, RelativeLayout 在性能上更好,因为在诸如 ListView 等控件中,
使用 LinearLayout 容易产生多层嵌套的布局结构,这在性能上是不好的。而 RelativeLayout 因其原理上的灵活性
,通常层级结构都比较扁平,很多使用LinearLayout 的情况都可以用一个 RelativeLayout 来替代,
以降低布局的嵌套层级,优化性能。所以从这一点来看,Google比较推荐开发者使用RelativeLayout,
因此就将其作为Blank Activity的默认布局了。