如何优化你的布局层级结构之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()方法
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的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中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。
如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析(一)...相关推荐
- Android中常见五种布局管理器——RelativeLayout、LinearLayout、FrameLayout、TableLayout、GridLayout
目录 布局管理器 RelativeLayout 常见属性 Relative的实践操作(实现软件更新界面) LinearLayout 常见属性 LinearLayout的实践操作(模范登录以及微信底部) ...
- Android官方开发文档Training系列课程中文版:布局性能优化之布局层级优化
原文地址:http://android.xsoftlab.net/training/improving-layouts/index.html 引言 布局是直接影响用户体验的关键部分.如果实现的不好,那 ...
- android 减少布局层级,Android 布局优化
布局优化主要从以下几点进行着手 减少布局层次 和 复杂度 优化绘制流程 按需加载布局 减少布局层次 和 复杂度 首先我们可以通过以下工具分析界面布局的结构 查看布局树工具:Hierarchy View ...
- 布局检测与优化(一):布局层级优化
在本文中,我们将全面解析UX中视觉层级的关键原则,探讨如何使用视觉层级来改进产品并优化用户体验. UX中的视觉层级 自设计之初,我们已经使用它来传达重要的信息.设计中的每个元素都应有助于改善用户体验, ...
- Android优化五:布局优化
1.减少布局层级 Google在API文档中建议View树的高度不宜超过10层. 以前我们用Eclipse写代码时,自动生成的模板是以LinearLayout为根节点的,但是后面变成了Relative ...
- 网页优化中,网站页面结构该注意什么?
网站页面结构即网页内容布局,网站页面结构的创建就是要对网页的内容进行规划布局,合理的网站页面结构总是很受搜索引擎蜘蛛的欢迎,网站页面结构也能直接影响页面的用户体验及相关性,还能影响网站整体结构及页面被 ...
- CVPR 2022 | CNN自监督预训练新SOTA:上交、Mila、字节联合提出具有层级结构的图像表征自学习新框架...
©作者 | 机器之心编辑部 来源 | 机器之心 来自上海交通大学.Mila 魁北克人工智能研究所以及字节跳动的研究者提出了一种具有层级语义结构的自监督表征学习框架,在 ImageNet 数据集上预训练 ...
- 顺风车Android性能优化之View布局优化
一.问题背景 在开发过程中,往往会听到 "性能优化" 这个概念,这个概念很大,比如网络性能优化.耗电量优化等等,对 RD 而言,最容易做的或者是影响最大的,应该是 View 的性能 ...
- Android性能优化系列之布局优化,Android程序员校招蚂蚁金服
25 26 rInflate方法关键代码 void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet ...
- 源码分析 merge 标签减少布局层级的秘密(Android Q)
源码分析 merge 标签减少布局层级的秘密(Android Q) 我在<Android 渲染性能优化--你需要知道的一切!>一文中介绍过,merge 标签用于减少 View 树的层次来优 ...
最新文章
- Pytorch张量tensor的使用
- R语言单因素方差分析(One-Way ANOVA)实战:探索性数据分析(EDA)、单因素方差分析模型结果解读(检查模型假设)、分析不同分组的差异TukeyHSD、单因素方差分析的结果总结
- 图的概念以及常见的图论问题介绍
- File,FileInfo;Directory,DirectoyInfo的区别
- LeakCanary: 让内存泄露无所遁形
- php大号字体代码,支持中文字母数字、自定义字体php验证码代码
- c++ 删除二叉树的子树_平衡二叉树
- 深入理解CSS盒模型 - 程序猿的程 - 博客园
- ultraedit26 运行的是试用模式_如何并行运行你的自动化测试?
- 穷举法求最大公共子序列C语言,算法--最长公共子序列(LongestCommon Subsequence, LCS)...
- 游戏开发筑基之判断回文数及static(C语言)
- python map对象
- 首都师范大学计算机技术复试分数线,2020年首都师范大学计算机应用技术考研分析...
- 西安北邮html5,西安北邮在线菜鸟的IT梦,给我一个支点,我就能撬动地球
- 解决 iphone6sp 换电池 电池胶断问题
- mysql数据库之基本函数,列属性,数据库管理
- EasyGUI-1:模块基础
- H3C交换机DHCP Snooping抑制局域网内非法dhcp
- 如何突出显示重点数据?
- 西红柿-生吃还是熟吃
热门文章
- 只开窗不镀锡_开窗也有大学问,只有老司机才懂这些车窗使用技巧
- MapReduce计算模型(二)
- 如何更高效、更系统地彻底搞懂3D视觉?
- uvalive 3126 Taxi Cab Scheme
- C语言联合体(union)的使用方法及其本质-union
- 获取URL Schema
- Codeforces Round #173 (Div. 2) E. Sausage Maximization —— 字典树 + 前缀和
- 大话设计模式--抽象工厂模式 Abstract Factory -- C++实现实例
- live555源码编译(win7+vs2010)
- Matlab连接字符串的方法