做安卓开发的小伙伴一定会被经常问到性能优化的问题,谈到性能优化,又必定会提到布局优化。例如:减少布局的层级,能用RelativeLayout的地方尽量使用RelativeLayout等等。但是很多人并不知道为什么用RelativeLayout性能就一定优于LinearLayout,或者说,用RelativeLayout性能就真的优于LinearLayout吗?鉴于此,本文就以个人所学知识进行一次探讨,由于本人所学有限,有出现错误的地方希望大家批评,指正!
俗话说,狮屎胜于熊便,直接用个简单布局,比较一下两种布局方式的性能。
LinearLayout Measure:0.764ms Layout:0.165ms draw:7.663ms
RelativeLayout Measure:2.183ms Layout:0.157ms draw:7.682ms
从这个数据来看,LinearLayout和RelativeLayout在layout和draw过程中的消耗相差无几,由于打点存在误差,所以两者的这个两个方法可以认为几乎是一样的。唯独两者的measure相差比较大,这是什么原因造成的呢?那接下来我们只能从源码的角度去寻找原因了。
先来看看RelativeLayout的onMeasure()方法干了什么
注:我只截取了部分关键代码
再来看看LinearLayout的onMeasure方法干了什么
与之前的RelativeLayout相比,LinearLayout的就简明了许多。先判断布局方向,然后再执行相应的测量方法,随便挑一个看看
注:我只截取了部分关键代码,代码有点长,看不太懂的地方可以看谷歌给我们的注释
下面我们来具体探讨一下两种布局方式的onMeasure方法。首先来看RelativeLayout的onMeasure方法,根据上面的截图,我们可以发现RelativeLayout对子View进行了两次measure方法,可是这是为什么呢?仔细想一下,似乎从relative这个关键词上就能大概得到答案。RelativeLayout中所有的子View的排列方式都是基于彼此的依赖关系,这个依赖关系和布局中的View的顺序并不相同,所以确定每个View的位置的时候,先要给每个子View进行排序。又因为RelativeLayout允许A,B 2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向分别进行一次排序测量。
再来看看LinearLayout的onMeasure方法。以measureVertical为例,父视图在对子视图进行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属性对性能是有影响的。
总结:
通过源码分析,我们会发现LinearLayout的性能是要比RelativeLayout来的高的,原因是RelativeLayout需要对所有子View进行两次测量,而LinearLayout只需要一次。而在LinearLayout中子View假如使用了weight属性,就也会进行两次测量,是会影响LinearLayout的测量性能的。但即便是这样,也是要由于RelativeLayout的。