概述
这篇博文作为对第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执行一次呢?网上有相关的答案,而且经过检查发现确实如此,不过这里再补充说明一下。这种原因归结于RootViewImpl
的performTraversals
方法中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的条件就是cancelDraw
和newSurface
所决定的。如果条件不满足,你会看到在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总是在真正绘制之前就已经显示的原因,因为它在那个时间点可以完全显示出来。
最后
以上就是欣喜画板为你收集整理的【安卓开发艺术探索】第4章 RelativeLayout和LinearLayout效率对比和分析测试一:10个简单View元素测试二:10个耗时View元素的全部内容,希望文章能够帮你解决【安卓开发艺术探索】第4章 RelativeLayout和LinearLayout效率对比和分析测试一:10个简单View元素测试二:10个耗时View元素所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复