专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
开发者全社区  ·  97岁李嘉诚 ·  7 小时前  
开发者全社区  ·  英区金融妲己 ·  17 小时前  
开发者全社区  ·  UCL色魔博士被抓 ·  昨天  
开发者全社区  ·  yc女同学的朋友圈 ·  2 天前  
开发者全社区  ·  你是来开房的,还是来入住的? ·  2 天前  
51好读  ›  专栏  ›  安卓开发精选

如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析(上)

安卓开发精选  · 公众号  · android  · 2016-09-14 08:32

正文

(点击 上方公众号 ,可快速关注)


来源:伯乐在线专栏作者 - 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 )







请到「今天看啥」查看全文


推荐文章
开发者全社区  ·  97岁李嘉诚
7 小时前
开发者全社区  ·  英区金融妲己
17 小时前
开发者全社区  ·  UCL色魔博士被抓
昨天
开发者全社区  ·  yc女同学的朋友圈
2 天前
开发者全社区  ·  你是来开房的,还是来入住的?
2 天前
CSDN  ·  游戏与算法的必经之路
8 年前
腾讯研究院  ·  互联网前沿法律动态周报| 2.21-2.27
8 年前
教你学风水转运  ·  你家iPhone还在用小白点吗?
7 年前