如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析
转载请注明出处: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()方法
- @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

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
- 这个很容易理解,下面的高同理*/
- 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;
- }
- }
结论
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干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码,即可关注。
如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析的更多相关文章
- 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- iOS-获取UIView的全部层级结构
在iOS中获取UIView的全部层级结构 应用场景 在实际 iOS 开发中,非常多时候都须要知道某个 UI 控件中包括哪些子控件,而且分清楚它们的层级结构和自个的 frame 以及 bounds ,以 ...
- 在iOS中获取UIView的所有层级结构 相关
在iOS中获取UIView的所有层级结构 应用场景 在实际 iOS 开发中,很多时候都需要知道某个 UI 控件中包含哪些子控件,并且分清楚它们的层级结构和自个的 frame 以及 bounds ,以便 ...
- 前端(七)—— 盒模型之display、overflow、隐藏、border、margin、样式支持,层级结构
display.overflow.隐藏.border.margin.样式支持,层级结构 一.盒模型之display 1.三种样式 block 块 inline 内联/行内 inline-block 内 ...
- VUE+ElementUI实现左侧为树形结构、右侧无层级结构的穿梭框
工作中遇到一个需求,需要将一个数据选择做成穿梭框,但是要求穿梭框左侧为树形结构.右侧为无层级结构的数据展示,ElementUI自身无法在穿梭框中添加树形结构,网上搜到了大佬封装的插件但是对于右侧的无树 ...
- MYSQL索引结构原理、性能分析与优化
[转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...
- Android上滑手势触发和不增加布局层级扩大点击区域
最近项目中需要实现手势上滑或者点击滑出界面的效果,实现上是利用GestureDetector,然后在onFling中判断,但遇到一个问题:手势上滑是针对整个布局的,但如果有对单独的View设置点击监听 ...
- 由浅入深探究mysql索引结构原理、性能分析与优化 转
第一部分:基础知识 第二部分:MYISAM和INNODB索引结构 1. 简单介绍B-tree B+ tree树 2. MyisAM索引结构 3. Annode索引结构 4. MyisAM索引与Inno ...
- 由浅入深探究mysql索引结构原理、性能分析与优化
摘要: 第一部分:基础知识 第二部分:MYISAM和INNODB索引结构 1.简单介绍B-tree B+ tree树 2.MyisAM索引结构 3.Annode索引结构 4.MyisAM索引与Inno ...
随机推荐
- springMVC源码分析--ModelFactory
ModelFactory是用来维护Model的,具体包含两个功能 (1)初始化Model (2)处理器执行后将Model中相应的参数更新到SessionAttributes中 1.初始化Model其实 ...
- 豌豆夹Redis解决方案Codis源码剖析:Proxy代理
豌豆夹Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy base ...
- 分析RunTime执行命令以及得到返回值
RunTime执行命令得到返回值 我们有在好好几篇博客里提到过RunTime,比如 JAVA之旅(二十三)--System,RunTime,Date,Calendar,Math的数学运算 Androi ...
- Python动态展现之一
首先: def f(): print('first') def g(): f() g() def f(): print('second') g() 结果: >>> first sec ...
- Java异常处理-----非运行时异常(受检异常)
非运行时异常(受检异常) 如果出现了非运行时异常必须进行处理throw或者try{}catch(){}处理,否则编译器报错. 1:IOException 使用要导入包import java.io.IO ...
- Effective C++ ——构造/析构/赋值运算符
条款五:了解C++默认编写并调用那些函数 是否存在空的类? 假设定义类为class Empty{}:当C++编译器处理过后会变成如下的形式: class Empty{ Empty(){} ~Empty ...
- 剑指offer面试题5 从头到尾打印链表(java)
注:(1)这里体现了java数据结构与C语言的不同之处 (2)栈的操作直接利用stack进行 package com.xsf.SordForOffer; import java.util.Stack; ...
- JDBC的java驱动安装
首先登陆mysql.com官方网站,download-->选中下面的community–>mysql connentor-->然后选中下面与平台无关的zip包,一般是第二个,完成下载 ...
- Dynamics CRM2015 非基础语言环境下产品无法新建的问题
该现象出现在2015版本上,之前从没注意过这个问题不知道以前的版本是否存在. 我的安装包的基础语言是中文,第一张图有添加产品的按钮,切换到英文环境下后就没有了,一开始以为是系统做了隐藏处理,但用工具查 ...
- ORA-12519: TNS:no appropriate service handler found 解决
select count(*) from v$process --当前的连接数select value from v$parameter where name = 'processes' --数据库允 ...