(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - yuiop逆流的鱼
链接:http://android.jobbole.com/84631/
点击 → 了解如何加入专栏作者
工作一段时间后,经常会被领导说,你这个进入速度太慢了,竞品的进入速度很快,你搞下优化吧?每当这时,你会怎么办?功能实现都有啊,进入时要加载那么多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()方法
@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
)