Android开发——LinearLayout和RelativeLayout的性能对比
0. 前言
我们都知道新建一个Android项目自动生成的Xml布局文件的根节点默认是RelativeLayout,这不是IDE默认设置,而是由android-sdk\tools\templates\activities\EmptyActivity\root\res\layout\activity_simple.xml.ftl这个文件事先就定好了的,在我们的理解里貌似LinearLayout的性能是要比RelativeLayout更优的,那SDK为什么会默认给开发者新建一个RelativeLayout呢?
同时作为顶级View的DecorView却是个垂直方向的LinearLayout,上面是标题栏,下面是内容栏,我们常用的setContentView()方法就是给内容栏设置布局。那么LinearLayout和RelativeLayout谁的性能更高呢,好吧其实我们都知道前者性能更高,那原因是什么呢?
1. 性能对比
问题的核心在于,当RelativeLayout和LinearLayout分别作为ViewGroup表达相同布局时谁的绘制过程更快一点。
Hierarchy Viewer是随Android SDK发布的工具,位于Android SDK/tools/hierarchyviewer.bat,使用它可以来检测View绘制的三大过程的耗时。如果不清楚View绘制的三大过程的,可以参考我之前写过的 Android开发——View绘制过程源码解析中详细的介绍过了,这里就不再赘述了。
通过网上的很多实验结果我们得之,两者绘制同样的界面时layout和draw的过程时间消耗相差无几,关键在于measure过程RelativeLayout比LinearLayout慢了一些。我们知道ViewGroup是没有onMeasure方法的,这个方法是交给子类自己实现的。因为不同的ViewGroup子类布局都不一样,那么onMeasure索性就全部交给他们自己实现好了。
所以我们就分别来追踪下RelativeLayout和LinearLayout的onMeasure过程来探索耗时问题的根源。本文原创,为保证错误及时更新请认准原创链接SEU_Calvin的博客。
1.1 RelativeLayout的onMeasure分析
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//...
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();
applyHorizontalSizeRules(params, myWidth);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
} views = mSortedVerticalChildren;
count = views.length;
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) {
width = Math.max(width, params.mRight);
}
if (isWrapContentHeight) {
height = Math.max(height, params.mBottom);
}
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会根据2次排列的结果对子View各做一次measure。这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和Xml布局中View的顺序不同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许ViewB在横向上依赖ViewA,ViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。
同时需要注意的是View.measure()方法存在以下优化:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
即如果我们或者我们的子View没有要求强制刷新,而父View给子View传入的值也没有变化(也就是说子View的位置没变化),就不会做无谓的测量。RelativeLayout在onMeasure中做横向测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统。这样会导致在子View的高度和RelativeLayout的高度不相同时(设置了Margin),上述优化会失效,在View系统足够复杂时,效率问题就会很明显。
1.2 LinearLayout的onMeasure过程
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
//LinearLayout会先做一个简单横纵方向判断,我们选择纵向这种情况继续分析
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//...
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
//... child为空、Gone以及分界线的情况略去
//累计权重
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
//计算
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
//精确模式的情况下,子控件layout_height=0dp且weight大于0无法计算子控件的高度
//但是可以先把margin值合入到总值中,后面根据剩余空间及权值再重新计算对应的高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
if (lp.height == 0 && lp.weight > 0) {
//如果这个条件成立,就代表 heightMode不是精确测量以及wrap_conent模式
//也就是说布局是越小越好,你还想利用权值多分剩余空间是不可能的,只设为wrap_content模式
lp.height = LayoutParams.WRAP_CONTENT;
} // 子控件测量
measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);
//获取该子视图最终的高度,并将这个高度添加到mTotalLength中
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
//...
}
源码中已经标注了一些注释,需要注意的是在每次对child测量完毕后,都会调用child.getMeasuredHeight()获取该子视图最终的高度,并将这个高度添加到mTotalLength中。但是getMeasuredHeight暂时避开了lp.weight>0的子View,因为后面会将把剩余高度按weight分配给相应的子View。因此可以得出以下结论:
(1)如果我们在LinearLayout中不使用weight属性,将只进行一次measure的过程。
(2)如果使用了weight属性,LinearLayout在第一次测量时避开设置过weight属性的子View,之后再对它们做第二次measure。由此可见,weight属性对性能是有影响的。
3. 总结论
(1)RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而后者只需一次,但是有weight属性存在时,后者同样会进行两次measure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,会引发效率问题,可以使用padding代替margin以优化此问题。
(3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。
结论中的第三条也解释了文章前言中的问题:DecorView的层级深度已知且固定的,上面一个标题栏,下面一个内容栏,采用RelativeLayout并不会降低层级深度,因此这种情况下使用LinearLayout效率更高。
而为开发者默认新建RelativeLayout是希望开发者能采用尽量少的View层级,很多效果是需要多层LinearLayout的嵌套,这必然不如一层的RelativeLayout性能更好。因此我们应该尽量减少布局嵌套,减少层级结构,使用比如viewStub,include等技巧。可以进行较大的布局优化。具体技巧的使用后面会继续写文总结。
最后希望大家多多点赞支持~
Android开发——LinearLayout和RelativeLayout的性能对比的更多相关文章
- 刷到血赚!字节跳动内部出品:722页Android开发《360°全方面性能调优》学习手册首次外放,附项目实战!
前言 我们平时在使用软件的过程中是不是遇到过这样的情况:"这个 app 怎么还没下载完!"."太卡了吧!"."图片怎么还没加载出来!".&q ...
- Android关于LinearLayout和RelativeLayout背景设置的区别
1.LinearLayout:设置背景时即设置android:background时,假如LayoutLayout设置了android:layout_width="wrap_content& ...
- Android开发--LinearLayout的应用
1.简介 LinearLayout为安卓三大常用布局中的线性布局.其中,线性布局又分为水平线性布局和垂直线性布局.视图如下所示:
- Android开发之布局--RelativeLayout布局
RelativeLayout 相对布局 true或false属性 Layout_centerHorizontal 当控件位于父控件的横向中间位置 Layout_centerVertical 当 ...
- Android开发——布局性能优化的一些技巧(一)
0. 前言 上一篇我们分析了为什么LinearLayout会比RelativeLayout性能更高,意义在于分析了这两种布局的实现源码,算是对一个小结论的证明过程,但是对布局性能的优化效果,对这两种布 ...
- Mac下搭建Eclipse Android开发环境
之前一直是用windows搞android开发,但windows这个性能也真是让人醉了,终于一狠心,砸锅卖铁买了Mac.然后就开始在Mac上搭建android开发环境, 其实也不麻烦,关键是找准下载地 ...
- (转) Android开发性能优化简介
作者:贺小令 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于PC的桌面应用程序.以上理由,足以 ...
- [素材资源] Android开发性能优化简介(非常不错的)
转自(http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost& ...
- 在 Android开发中,性能优化策略十分重要
在 Android开发中,性能优化策略十分重要本文主要讲解性能优化中的布局优化,希望你们会喜欢.目录 示意图 1. 影响的性能 布局性能的好坏 主要影响 :Android应用中的页面显示速度 2. 如 ...
随机推荐
- Linux系统下常用的磁盘管理命令——du / df / fdisk / mount / xxd
之前使用虚拟机体验Linux操作系统的使用,一般使用默认的磁盘分区设置,也很少涉及磁盘管理操作,且总有删除重装作为后盾.在安装Ubuntu双系统后,在使用过程中遇到了磁盘分区不合理导致的/boot分区 ...
- datetable dt.select使用詳情
当你从数据库里取出一些数据,然后要对数据进行整合,你很容易就会想到: 1DataTable dt = new DataTable();//假设dt是由"SELECT C1,C2,C3 FRO ...
- Linux Ubuntu安装sogou中文输入法
在linux下开发,有时还是需要使用中文输入法的,每次安装的时候都觉得痛苦,这次做下记录,方便下次安装. 安装fcitx 安装sogou输入法之前,需要安装fcitx(Free Chinese Inp ...
- Linux下Java性能监控
Linux下Java性能监控 一.JVM堆内存使用监控 获取thread dump的3种方法: 1)使用$JAVA_HOME/bin/jcosole中的MBean,到MBean>com.sun. ...
- 维度属性的KeyColumns如果是Integer类型,那么维度表中该列的值不能有为null的
如果维度属性的 KeyColumns的DataType设置为了Integer类型,那么要注意该维度属性列在数据库中不能有为null的值. 例如下图中我们有维度DIM_Vehcile,其中有个维度属性叫 ...
- Oracle与MySQL使用区别
与MySQL通过创建不同的数据库来存储表 Oracle提出表空间(tablespace)的概念作为逻辑上的存储区域来存储表, 而不同的表空间由不同的用户来管理 用户可以授予权限或角色 举例: 使用PL ...
- python爬虫练习 -- 签名器+GUI界面(Tkinter)
效果图: 实现步骤如下: 实现原理:其实就是套了一层GUI的壳,主要还是爬虫抓取某个网站返回的数据,然后利用python自带的GUI工具包Tkinter来实现gui界面: 1.爬虫分析: 目标站点:h ...
- python class用法
创建一个名为 Restaurant 的类,其方法 __init__() 设置两个属性: name 和 type 1.创建一个名为 describe_restaurant() 的方法,前者打印前述两项 ...
- VirtualBox + CentOS详细安装教程
一.前期工作准备 电脑虚拟化开启(必要工作)大致流程: a.电脑开机时长按F12(F10)进入BIOS界面; b.依次选择Configuratio > Intel Virtual Technol ...
- iview-cli 项目、iView admin 代理与跨域问题解决方案
iview-cli 项目.iView admin 跨域.代理问题解决方案 在webpack.dev.config.js文件中: 添加: devServer: { historyApiFallback: ...