当前位置: 首页 > 编程日记 > 正文

【转】RelativeLayout和LinearLayout及FrameLayout性能分析

原文:http://blog.csdn.net/hejjunlin/article/details/51159419

工作一段时间后,经常会被领导说,你这个进入速度太慢了,竞品的进入速度很快,你搞下优化吧?每当这时,你会怎么办?功能实现都有啊,进入时要加载那么多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()方法
 @Overrideprotected 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 viewwidth += 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 viewheight += 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()方法
  @Overrideprotected 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 0oldHeight = 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属性对性能是有影响的,而且本身有大坑,请注意避让。

小结

从源码中我们似乎能看出,我们先前的测试结果中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的子viewfinal 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 toomaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and widthmaxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and widthfinal 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);}}}

加了一个嵌套,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等一些技巧。

转载于:https://www.cnblogs.com/chenlong-50954265/p/5942182.html

相关文章:

SQL Tips

出自&#xff1a;http://blog.csdn.net/etmonitor/一.怎样删除一个表中某个字段重复的列呀,举个例子表[table1]id name1 aa2 bb3 cc1 aa2 bb3 cc我想最后的表是这样的id name1 aa2 bb3 cc回答:将记录存到临时表#t中&#xff0c;重复的记录只存一条&#xff0c;然后将临时…

98年“后浪”科学家,首次挑战图片翻转不变性假设,一作拿下CVPR最佳论文提名​...

出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;刚刚结束的CVPR大会&#xff0c;总共收到6424篇论文中&#xff0c;仅有26篇获得最佳论文提名&#xff0c;占0.4%的比例。其中&#xff0c;康奈尔大学大四学生林之秋&#xff0c;以第一作者身份提交的“Visual Chiralit…

MySQL导入导出数据和结构

1. mysql导出数据和结构使用mysqldump命令1.1 导出全库连带数据mysqldump -u root -p app_test > app_test.sql1.2 导出指定的表&#xff0c;table1连带数据mysqldump -u root -p app_test table1 > app_test_table1.sql1.3 导出多张表&#xff0c;table1&#xff0c;tab…

图表君聊docker-仓库

图表君聊docker-仓库 今天我们来继续聊docker&#xff0c;上篇文章我们介绍了docker里的Container.今天来继续三大概念中的最后一个--仓库&#xff08;Repository)。 当我做好了一个Image&#xff0c;我该怎么和其他人分享呢&#xff1f;答案很简单&#xff0c;把他push到一个仓…

正则表达式经典教程

作者&#xff1a;ET Dreams http://blog.csdn.net/etmonitor/Regular Expressions (1) ---- What is Regular Expressions?正则表达式是常见常忘&#xff0c;所以还是记下来比较保险&#xff0c;于是就有了这篇笔记。希望对大家会有所帮助。J1&#xff0e;什么是正则表达式...…

发布了!2020年AI人才发展报告,最高补助1000万!

最近&#xff0c;程序员届有一个重大好消息&#xff0c;可能很多人还不知道&#xff0c;那就是&#xff1a;国内某些城市已经开始程序员人才补贴了&#xff01;对于人工智能公司的项目开发、人才引进、科技研发&#xff0c;最高按照国拨经费的30%给予配套支持&#xff0c;单个项…

C++资源之不完全导引(上)

发信人: NULLNULL (空空), 信区: VC标 题: C资源之不完全导引(转载)发信站: 武汉白云黄鹤站 (2005年05月05日01:42:54 星期四), 站内信件C资源之不完全导引&#xff08;完整版&#xff09;来源&#xff1a;www.csdn.net-----------------------------------------------------…

HDU 4467 分块

题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid4467 题意&#xff1a;给定n个点m条边的无向图&#xff0c;点被染色(黑0/白1)&#xff0c;边带边权。然后q个询问。询问分为两种&#xff1a; Change u:把点u的颜色反转(黑变白&#xff0c;白变黑)&#xff0c;…

ASP.NET重用代码技术 - 代码绑定技术

作者&#xff1a; 苏红超 导读 代码绑定是ASP.NET提供的一个重要的新技术。本文将会为您展示如何利用代码绑定技术来实现Web页面表示层和商业逻辑代码的分离&#xff0c;并建议您使用代码绑定技术实现代码的可重用。在接下来的另外一篇文章当中&#xff0c;我们会给出另外…

【ZooKeeper Notes 3】ZooKeeper Java API 使用样例

查看PDF版本 转载请注明&#xff1a;ni掌柜 nileadergmail.com ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务框架&#xff0c;包含一组简单的原语集合。通过这些原语言的组合使用&#xff0c;能够帮助我们解决更高层次的分布式问题&#xff0c;关于Zo…

一站式了解多模态、金融、事理知识图谱构建指南 | AI ProCon 2020

整理 | 许爱艳出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】7 月 3-4 日&#xff0c;由 CSDN 主办的第三届 AI 开发者大会&#xff08;AI ProCon 2020&#xff09;在线上举行。本次大会有超万人报名参与&#xff0c;参与人群覆盖 60 领域、5000…

CentOS7安装配置redis-3.0.0

一.安装必要包 yum install gcc 二.linux下安装 #下载 wget http://download.redis.io/releases/redis-3.0.0.tar.gz tar zxvf redis-3.0.0.tar.gz cd redis-3.0.0 #如果不加参数,linux下会报错 make MALLOClibc 安装好之后,启动文件 #启动redis src/redis-server &#关闭re…

ASP.NET重用代码技术 - 用户控件技术

作者&#xff1a; 苏红超 使用ASP.NET中的代码绑定技术来使得代码重用变得简单可行。我们发现&#xff0c;利用代码绑定技术我们可以容易的将我们的代码和内容分离开来&#xff0c;利用它可以建立可重用的代码&#xff0c;只是这种技术本身也存在着一些局限性。在本文中&…

liunx 下dhcp中继及服务器配置

dhcp:动态主机配置协议 使用udp协议 端口为67&#xff08;服务&#xff09;&#xff0c;68&#xff08;客户&#xff09; 作用&#xff1a;动态分配地址等参数 工作模式 1. 手工 manual server—地址池 &#xff08;ip—mac&#xff09; 2222----1.1.1.1 dhcpclient ------地址…

PyCharm vs VSCode,是时候改变你的 IDE 了!

作者 | Sohaib Ahmad译者 | 鹿未来&#xff0c;责编 | 屠敏头图 | CSDN 下载自东方 IC出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;也许是我有些落伍&#xff0c;或者也是因为JetBrains在Python IDE的市场上占有很大的份额&#xff0c;以至于直到最近我才发现&a…

(转)Linux 下 查看以及修改文件权限

场景&#xff1a;Linux环境下远程部署项目&#xff0c;发现因为文件权限问题&#xff0c;不能执行远端的可执行文件。问题还没解决&#xff0c;待议。。。 1 查看权限 在终端输入: ls -l xxx.xxx &#xff08;xxx.xxx是文件名&#xff09; 那么就会出现相类似的信息&#…

软件文档知多少?

作者&#xff1a;由于本人在无数网站看到此文 无法确定第一作者 请作者与本人联系如今&#xff0c;软件开发越来越复杂&#xff0c;软件功能也越来越丰富。而几乎所有成熟的商业软件&#xff0c;都是靠一个开发团队齐心协力的血汗结晶。“罗马不是一天建成的&#xff01;”&…

在 VMware ESXi 5.0 上安装万兆网卡驱动

2012年02月28日 | 标签: vmware esxi | 作者&#xff1a;vpsee 转载自&#xff1a;http://www.vpsee.com/2012/02/intall-network-card-driver-on-vmware-esxi-5-0/ 昨天刚发现新购的 Dell PowerEdge R710 服务器上配的 Intel Ethernet Server Adapter X520-T2 万兆网卡居然在…

漫谈 ClickHouse 在实时分析系统中的定位与作用

ClickHouse 是一款由俄罗斯Yandex公司开源的OLAP数据库&#xff0c;拥有着卓越的性能表现&#xff0c;在官方公布的基准测试中&#xff0c;ClickHouse的平均响应速度是Vertica的2.63倍、InfiniDB的17倍、MonetDB的27倍、Hive的126倍、MySQL的429倍以及Greenplum的10倍。自2016年…

Js+Dhtml:WEB程序员简易开发工具包(预先体验版)

作者&#xff1a;lshdic http://blog.csdn.net/lshdic/<HTML> <HEAD> <META http-equivContent-Type contenttext/html;charsetgb2312> <META nameGemeratpr content网络程序员伴侣(Lshdic)2005_开拓版> <TITLE>LD5工具</TITLE> <st…

残差网络的前世今生与原理 | 赠书

本文内容节选自《深度学习之模型设计&#xff1a;核心算法与案例实践》&#xff0c;作者言有三。本书详解了数十年来深层卷积神经网络模型的主流设计思想&#xff0c;理论讲解细致&#xff0c;实战案例丰富&#xff0c;是熟练掌握深度学习模型使用的必备参考资料。想要了解关于…

python---简单数据库

2019独角兽企业重金招聘Python工程师标准>>> #simple database#people people {Alice:{phone:2341,addr:Foo drive 23},Beth:{phone:9102,addr:Bar street 42},Ceil:{phone:3158,addr:Baz avenue 90} }#describe labels {phone:phone number,addr:address }name …

Linux系统之路——如何在CentOS7.2安装MySQL

一、Mysql 各个版本区别&#xff1a;1、MySQL Community Server 社区版本&#xff0c;开源免费&#xff0c;但不提供官方技术支持。2、MySQL Enterprise Edition 企业版本&#xff0c;需付费&#xff0c;可以试用30天。3、MySQL Cluster 集群版&#xff0c;开源免费。可将几个M…

Vml+Dhtml:制作一个应用渐变颜色效果不错的进度条

//原作:风云舞,载自: http://www.lshdic.com/bbs<HTML xmlns:v> <HEAD> <META http-equivContent-Type contenttext/html;charsetgb2312> <Meta nameGemeratpr content网络程序员伴侣(Lshdic)2004> <TITLE>效果不错的VML进度条</TITLE> &l…

使用inno setup打包程序完整脚本(.net框架检测,重复安装检测)

; 脚本由 Inno Setup 脚本向导 生成&#xff01;; 有关创建 Inno Setup 脚本文件的详细资料请查阅帮助文档&#xff01;#define MyAppName "小小鸟软件"#define MyAppVersion "2012.2.29"#define MyAppPublisher "小小鸟科技"#define MyAppURL &…

GPT-3到来,程序员会被AI取代吗?

作者 | Frederik Bussler译者 | 弯月&#xff0c;编辑 | 屠敏题图 | 自东方 IC出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;2017年的时候&#xff0c;曾有研究人员问&#xff1a;到2040年人工智能是否承担起大多数的编程工作&#xff1f;如今OpenAI的G…

iOS开发几年了,你清楚OC中的这些东西么!!!?

iOS开发几年了,你清楚OC中的这些东西么!!!? 前言几年前笔者是使用Objective-C进行iOS开发, 不过在两年前Apple发布swift的时候,就开始了swift的学习, 在swift1.2发布后就正式并且一直都使用了swift进行iOS的开发了, 之后就是对swift持续不断的学习, 近来swift3.0的发布, 更多的…

在做会员资料修改时,实现下拉菜单的默认项定位

作者&#xff1a;lshdic http://blog.csdn.net/lshdic/ <!--在写一个交友网站时碰到的问题,就是当会员修改资料时&#xff0c;如何定位SELECT的菜单列默认项&#xff0c;不过很容易就解决了--> <HTML> <HEAD> <META http-equivContent-Type contenttex…

NFS 文件共享的创建过程

nfs 文件共享的服务器 nfs服务需要两个软件包nfs-utils和portmap 启动nfs服务 # service portmap start # service nfs start # chkconfig nfs on 开机自动启动 配置文件&#xff1a; /etc/exports 想要共享某个文件则编辑配置文件 共享目录 共享IP&#xff08;共享属性&…

行业新风向!AI人才缺口30万,单个项目最高补贴1000万元!

最近&#xff0c;程序员届有一个重大好消息&#xff0c;可能很多人还不知道&#xff0c;那就是&#xff1a;国内某些城市已经开始程序员人才补贴了&#xff01;对于人工智能公司的项目开发、人才引进、科技研发&#xff0c;最高按照国拨经费的30%给予配套支持&#xff0c;单个项…