【安卓开发艺术探索】第4章 RelativeLayout和LinearLayout效率对比和分析

这篇博文作为对第4章的扩充,将关注重点放在了LinearLayout和RelativeLayout的效率对比上。首先来看我的一系列测试:

测试一:10个简单View元素

LinearLayout和RelativeLayout各包含10各简单的View元素,呈竖直排列。测试结果会令你惊讶:

//单位是ns
LinearLayout:
185937 
176770 
RelatvieLayout:
141562
140052

发现竟然RelativeLayout在某些情况下比LinearLayout还快!当时我也震惊了,不过仔细一想,会不会因为包含的元素过于简单,使得子元素的多重测量体现的不够明显才导致这种现象呢?

测试二:10个耗时View元素

于是我写了一个TestView,重写了onMeasure方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

让每次子View的测量都休眠10ms,来模拟复杂的子View情况。迫不及待想看测试结果了吧?如下:

LL:
92352500
91181302
RL:
203691198
201898072

发现RelativeLayout确实比LinearLayout耗时更多,而且似乎看到了那个美妙的比例:1:2。这个比例的产生原因可能很多人心里都有数,就是所谓RelativeLayout比LinearLayout更耗时的原因是要进行两次测量,而LinearLayout只用进行一次。这个说法我表示同意,在仔细读了源码之后,确实认为这个区别是最容易造成两者效率差异的地方,当然通过测试一你也惊喜发现,在某些情况下RL比LL还快哦~

关于这两个Layout的源码分析,其实也不算很难,RelativeLayout的measure过程有一步调用了sortChildren,这个方法比较有意思,而且也是RL能实现复杂的依赖关系布局的核心部分了,它有用到图的数据模型DependencyGraph,从而管理其中节点之间的依赖性。把不依赖任何节点的节点作为根节点,对根节点进行遍历,把此节点装到要返回的数组中,裁剪掉依赖这个节点的边,如果又产生了新的根节点,则纳入到要遍历的集合中。一直这样进行下去,最后再比较原始元素总数和最终装入返回数组的总数是否相等,如果不相等则说明图中出现了闭环报错。否则返回最终的排序结果。(注意这里是对竖直方向和水平方向分开进行的)比如有元素A,B,C,B中toLeftOf A,C中toRightOf A toLeftOf B,也就是B->A,C->A,C->B,这样最后水平方向的排序结果就是A,B,C,只有这样的先后处理顺序才能确保依赖正确。这个过程的时间复杂度也不算高。如果有兴趣,可以看看RelativeLayout中是如何通过mRules[]数组记录依赖的,挺有趣的。LL就不多分析了,最后贴一下可以参考的链接,供大家参考:
http://www.jianshu.com/p/8a7d059da746


插曲

为何在调试的时候总是发现onMeasure、onLayout要被执行两次,dispatchDraw执行一次呢?网上有相关的答案,而且经过检查发现确实如此,不过这里再补充说明一下。这种原因归结于RootViewImplperformTraversals方法中newSurface这个布尔值,如果它是true则说明是新的视图,需要重新进行一次遍历(包括measure,layout)才进行draw。看如下的代码(api25):

        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }

                performDraw();
            }
        } else {
            if (viewVisibility == View.VISIBLE) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

我们知道draw过程的总入口在performDraw方法上,而调用performDraw的条件就是cancelDrawnewSurface所决定的。如果条件不满足,你会看到在else部分中有scheduleTraversals的调用,说明又会重新进行一次遍历。可以解释为何measure,layout会被调用两次,dispatchDraw调用一次的原因了。关于为何官方要这样做,有一段注释或许能解答:

                        // If we are creating a new surface, then we need to
                        // completely redraw it.  Also, when we get to the
                        // point of drawing it we will hold off and schedule
                        // a new traversal instead.  This is so we can tell the
                        // window manager about all of the windows being displayed
                        // before actually drawing them, so it can display then
                        // all at once.
                        newSurface = true;

翻译过来就是:当我们创建一个新的视图时,需要完全重新绘制它。另外当我们在绘制之前会拖延一下并重新进行一次遍历。这也是为何我们说所有window的window manager总是在真正绘制之前就已经显示的原因,因为它在那个时间点可以完全显示出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值