例说 Constraint Layout(三)—— 性能测评

栏目: IOS · Android · 发布时间: 5年前

内容简介:去年写完《例说 Constraint Layout(一)—— 概论》后过去一年多了,怎么《例说》的(二)、(三)就“难产”了?究其根本的原因,一是尝试实测 Constraint Layout(CL) 性能时,用 DDMS(Dalvik Debug Monitor Service)查看后发现性能没有明显提升;二则官方也说,如果项目中原有的布局没有性能问题的话,不必切换成 CL,又正好面临安装包过大的问题,就没有引入 CL;三是测试一次性能特别麻烦,忙起来就拖到新版的 AS 甚至不集成 DDMS 了(用命令行

0 引言

去年写完《例说 Constraint Layout(一)—— 概论》后过去一年多了,怎么《例说》的(二)、(三)就“难产”了?究其根本的原因,一是尝试实测 Constraint Layout(CL) 性能时,用 DDMS(Dalvik Debug Monitor Service)查看后发现性能没有明显提升;二则官方也说,如果项目中原有的布局没有性能问题的话,不必切换成 CL,又正好面临安装包过大的问题,就没有引入 CL;三是测试一次性能特别麻烦,忙起来就拖到新版的 AS 甚至不集成 DDMS 了(用命令行去 Platform 下也打不开了)。幸好这时候遇到了 《Understanding the performance benefits of ConstraintLayout》[1]这篇文章(作者为 Google 工作,也是可伸缩布局 FlexboxLayout 的作者),本文参照了其测试方式,全面地对 CL 的性能进行了评测。本文结论浓缩成一句话的话,就是:在各种页面设计下,提升有多有少,但 CL 的性能确实是最佳的! 正文详细讨论了测试方法并分析了不同情况的测量结果,有时间的读者且听我细细道来,没有的记住上一句总结即可:)

1 概述

1.1 评判 Layout 性能好坏的标准

先简单介绍一下本文用于评判 Layout 性能好坏的依据。在 Android 中,加载布局并最终将其绘制到屏幕上的过程主要包括 3 步:

  • 测量(Measure)
  • 布局(Layout)
  • 绘制(Draw)

这三个步骤都是从布局的根节点开始,自顶向下遍历视图树完成的。

就像《例说 Constraint Layout(一)》中提到的,RelativeLayout(RL)需要至少调用两次子 View 的 onMeasure() 方法才能完全确定布局中所有 View 的尺寸和位置,使用了 android:layout_weight 属性的 LinearLayout、使用了 android:stretchColumns / android:shrinkColumns 的 TableLayout,也都需要遍历两次子 View 的 onMeasure() 方法。随着布局层级的叠加,Measure 的耗时也呈指数型地增加。可以预期到更加扁平化的 CL 布局,其最主要的性能提升在于 Measure 阶段的速度提高,本文的测量也主要专注于测量 onMeasure 阶段的耗时。

又因为 CL 的灵活性,比起传统布局它可以省略中间不必要的一些 View 或 ViewGroup,所以其 Layout 和 Draw 速度也会有一定小幅提升[2]。结合考虑到测量的方便性,本文将 Layout 的耗时也纳入了考量。

1.2 采用的性能比较方法

下面简单介绍一下本文用到的性能测量方式。 我最开始尝试使用的是 DDMS,使用其统计得到布局耗费的 onMeasure & onLayout 时间,后来也尝试过使用 AS 新功能:Android Profiler 的 CPU Profiler 来测量,这两种方式测得的效果都不太理想,无法看出 CL 比其它布局更快速。推测下来由三个原因造成:

  1. 像 Android Profiler 这种测量工具,本来就极其消耗计算机的资源,相信小伙伴们使用的时候也发现了,打开 工具 后AS 界面会明显出现卡顿。所以其测量本身会对测量结果有影响,CL 同其他布局间性能上几毫秒的微妙差异,相比起使用 Android Profiler 对测量环境的人明显可感知到的影响,完全可以忽略不计;
  2. 万年非酋作者天赋技能触发,虽然根据理论选择了理应性能比较差的布局同 CL 比较,但是最后证明这种情况下 CL 的性能提升最少(平均仅比十分之一略多,下文会具体分析),看到的细微差别也就容易被我当成误差忽略了;
  3. 打开一个 Activity,统计其 Measure 和 Layout 的时间实在不是一个好方法,即使可以测量十次求平均,但这种方式统计繁琐,单词测量数据量小,效率太低了。
  • OnFrameMetricsAvailableListener

所以我最终放弃了前述的两种方式,而参照《Understanding the performance benefits of ConstraintLayout》[1]中的测量方法,使用 Android API Level 24 引入的 OnFrameMetricsAvailableListener 接口来比较各布局 Measure 和 Layout 的耗时之和。在代码中使用 OnFrameMetricsAvailableListener 的代码如下:

private val frameMetricsHandler = Handler()
private val frameMetricsAvailableListener = Window.OnFrameMetricsAvailableListener {
    _, frameMetrics, _ ->    val frameMetricsCopy = FrameMetrics(frameMetrics)    // Layout measure duration in Nano seconds
    val layoutMeasureDurationNs = frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION)
    Log.d(TAG, "layout_Measure(ns): " + layoutMeasureDurationNs)
}
……
override fun onResume() {    
    super.onResume()
    window.addOnFrameMetricsAvailableListener(frameMetricsAvailableListener, frameMetricsHandler)
}
override fun onPause() {    
    super.onPause()
    window.removeOnFrameMetricsAvailableListener(frameMetricsAvailableListener)
}

我们的关注点在于测量/布局的性能,因此使用了 FrameMetrics.LAYOUT_MEASURE_DURATION ,FrameMetrics的其他可测量时长详见官方文档[4]。

加入了前述代码后,在获取到时间信息之后,就会触发 frameMetricsAvailableListener() 回调。

为了使结果更精确,每一个布局的一次测量都是绘制 100 次取平均的结果。即每 100 ms,切换一下根节点的 MeasureSpec(match_parent 和固定值间切换,以确保整个布局被重新测量和布局),切换 100 次后,计算平均耗时。代码如下:

private fun measureAndLayoutWrapLength(round: Int?, container: ViewGroup, w: Int, h: Int) {    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.AT_MOST)    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.AT_MOST)
    container.measure(widthMeasureSpec, heightMeasureSpec)
    container.layout(0, 0, container.measuredWidth, container.measuredHeight)
}
private fun measureAndLayoutExactLength(round: Int?, container: ViewGroup, w: Int, h: Int) {    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY)    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY)
    container.measure(widthMeasureSpec, heightMeasureSpec)
    container.layout(0, 0, container.measuredWidth, container.measuredHeight)
}
  • 环境
  1. 除了最后部分比较 CL 不同版本用到了 ConstraintLayout 1.1.3 版本,其余测量均使用 CL 1.0.2 版本;
  2. 测试机用的是小米 5;
  3. 不同的 UI 界面可能不是同一天测量的,但是同一个 UI 界面用于比较的、分别用 CL 和传统布局实现的界面必然是一起测量的,以确保机器处于相同状态;
  4. 重点重申:每一个结果都是间隔 100 ms 、测一百次的结果的平均。

2 实测

2.1 官方 Demo 页面

先来看看官方 Demo 中的页面,其 CL 和传统布局耗时的对比。布局真实的展示效果见 Fig. 1,左边为传统布局,右边为约束布局。为了和《Understanding the performance benefits of ConstraintLayout》[1]文中的结果对比,即使文中的 CL 和传统 RL 的展示效果略有不同,也没有改写布局文件使两者保持一致。而后文对比的我自己创建的布局,会尽我所能使其展示效果保持相同。

Fig. 1 性能测试用 Demo 中的传统 Layout 和 CL

上面两个布局 100 次 Measure + Layout 平均耗时对比如下:

Fig.2 Demo 中 CL 和传统 Layout 耗时对比

第一次安装完跑下来发觉 CL 性能的提升只有 21% 左右,只有《Understanding the performance benefits of ConstraintLayout》[1]文中结果 40% 的一半,因为效果不够好,又连续多测了几次,并尝试采用不同的根节点的 MeasureSpec 固定值时的尺寸(全屏和 1080*1920)。可以看到之后几次测量,传统布局和约束布局的时间都有提升,且约束布局的提升特别明显,其性能比传统布局提高了 65% 以上。所以,即使采用了 100 次取平均,手机当时的状态对测量结果还是有很大影响的,后文也尝试在不同天进行测试,结果也确实不同,我们可以做的就是用于比较的两个布局的数据,必须在同一段短时间内测量(本文所有比较数据都在一分钟以内测量完成)。Demo 的例子中,虽然手机状态有所变化,但可以肯定的是,CL 要比传统布局更快。

另,此节中页面根节点的 MeasureSpec 固定值时的尺寸不同,对结果并没有影响;而 2.4 节中,此值对结果则有一定影响。

2.2 磁贴风 LL(weight)和 CL

既然验证了《Understanding the performance benefits of ConstraintLayout》[1]一文的结果,我们回过头来看看我最开始使用 DDMS 和 Android Profiler 的 CPU Profiler 来比较,并没有得到明显性能差异的页面。这个页面是仿造 Windows 磁贴风写的,手机上显示效果如下,左边是 LL,右边是 CL:

Fig. 3 性能测试用磁贴风的传统 LL (weight) 和 CL

当初选择这个样式其实是经过思考的,根据 Android 源代码,使用了 android:layout_weight 属性的线性布局的子节点必须遍历两遍 Measure,理应性能比较差,且层级越深,性能越差。Fig. 4 左边展示了 LL 布局前半部分的层级结构,可以看到仅仅是前半部分,层级就很复杂了,每一层都是通过使用了 android:layout_weight 属性的 LL 实现的,多层嵌套,最多可以达到 7 层,即其叶节点会跑 2^6 次 OnMeasure 方法。

而 CL 则是扁平的单层结构(见 Fig. 4 右半边),使用了 Guideline 方式来实现磁贴风效果,同 LL 相比大体结构一致,仅细微处(黑边粗细)略有不同。

Fig. 4 磁贴风的传统 LL (weight) 和 CL 的层级结构

然而和预期的不太一样,CL 的性能提升并没有想象的多,平均只有 10% 左右,见下图 Fig. 5。如此小的差距,使用对手机环境影响比较大的 DDMS 或 CPU Profiler 确实会无法对性能提高和误差加以区分辨别。

Fig. 5 磁贴风 CL 和 LL (weight) 耗时对比

2.3 传统 LL (weight) 和不同写法 CL

为什么比起使用了 android:layout_weight 属性的、性能理应比较差的 LinearLayout(LL), CL 并没有明显的性能优势呢?不禁怀疑是不是约束布局的 Guideline 属性其实也属于比较耗时的属性,所以决定要比较一下:使用了不同 CL 属性实现的相同显示效果的 UI 界面的性能(Fig. 6),左边是使用了 android:layout_weight 属性的传统线性布局,右边从上之下分别是使用了: layout_constraintXXXXXX_biaslayout_constraintXXXXXX_chainStylelayout_constraintGuide_XXXXXX 属性写成的效果完全一致的约束布局(由于页面的下半部分无内容,裁剪掉以节约展示空间)。

Fig. 6 性能测试用传统 LL (weight) 和不同写法 CL

Fig. 7 是测量结果,可以看到应用不同 CL 属性,其性能差别并不大:比起 LL,提升都在 40% 左右,使用 Guideline 方式甚至性能会更优秀一点。在这个比较简单的布局中,CL 的性能提升就比较明显,比 2.2 中的磁贴风要明显很多,猜测当布局明显变复杂,每一个元素的上下左右边都同其它元素相关时,CL 的性能会有一定程度的下降。

Fig. 7 ActionBar 中不同 CL 写法和 LL (weight) 耗时对比

2.4 网格风 CL 和 RL

除了混合布局(2.1 节)、线性布局(2.2 节、2.3 节),当然也想将约束布局同我们最常用、也是灵活性很高的相对布局比较一下。下图 Fig. 8 就是分别使用 RL(左)和 CL(右)实现的一个价目表页面,两者的显示效果已经完全对齐。

Fig. 8 性能测试用网格风 RL 和 CL

Fig. 9 分别比较了在不同的日子测量、根节点的 MeasureSpec 固定值使用全屏和 1080*1920 的性能,可以看到结果不尽相同,所以说两者对布局的性能确实是有影响的,但是总体说来还是那句话:CL 还是比 RL 性能要有所提升。

Fig. 9 网格风 CL 和 RL 耗时对比

2.5 不同版本 ConstraintLayout 依赖库

由于在我写《例说 Constraint Layout》系列文章以来,Google 仍然在不断优化更新 ConstraintLayout 依赖库,最后也希望简单测试一下最新版本的 CL 是否在速度方面有进一步提高,所以升级到了 1.1.3 版本的 CL 库,比较了 Chains 写法的 CL 和 Weight 写法的 LL 的性能。界面样式见 Fig. 6,测量结果见 Fig. 10。

Fig. 10 CL 1.1.3 版本 CL (chains) 和 LL (weight) 耗时对比

结果略有点出人意料:

  1. 首先,头两次测量下来,CL 的性能并不比 LL 好。(图表中只记录了第二次,第一次的数据因为我以为是自己搞错了,没有记录下来。)可见手机的运行状态对布局性能的影响还是挺大的;
  2. 排除开始的异常数据,1.1.3 版本的 CL 平均性能提升(~24%)比起 1.0.2 版本的(39%)要低(不过和 2.3 节比较,不同 CL 版本对应的 LL 的绝对时长也不相同,此处只能比较相对提升了,一定程度上会受机器当时状态影响)。

当然,上述的测量只是试水性质的,针对一个非常简单的单方向的布局的,也许在其它的界面设计下,新版的 CL 会有更优异的表现,完整的结论需要更多详细的测量才能得出。

小结

先来归纳几个点:

  1. 布局性能的测量受测试机器当时的状态、布局的设计两个因素的影响比较大,但仍旧可以很肯定地说,约束布局 CL 的性能要比传统布局(混合、相对、线性等)有提升;
  2. CL 的性能同用到的不同属性关系不大,一般说来会比传统布局提升 10% ~ 45%,常见在 30% 左右;
  3. 对于特别复杂、或某些特殊的界面,CL 的性能提升可能不那么显著,10% 左右;
  4. 不同的 ConstraintLayout 依赖版本,并不保证版本越新性能越好,至少在某个简单的界面下,1.1.3 的版本性能并不比 1.0.2 的优秀。

所以大家对安装包大小没有特别限制的话,写新的布局可以多尝试尝试约束布局 CL,毕竟在大部分情况下它都是性能最好的那一款,灵活性也足够。当然,原有的许多没有性能问题的界面,也没有必要强求改变。

参考资料

[1] Takeshi Hagikura. Understanding the performance benefits of ConstraintLayout,Aug. 2017

[2] Takeshi Hagikura. Exploring New Android Layouts,Apr. 2017

[3] Window.OnFrameMetricsAvailableListener()

[4] FrameMetrics

作者简介:opalli,天天P图 Android 工程师

文章后记: 天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习! 加入我们: 天天P图技术团队长期招聘:(1) 图像处理算法工程师,(2) Android / iOS 开发工程师,期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@tencent.com


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Cyberwar

Cyberwar

Kathleen Hall Jamieson / Oxford University Press / 2018-10-3 / USD 16.96

The question of how Donald Trump won the 2016 election looms over his presidency. In particular, were the 78,000 voters who gave him an Electoral College victory affected by the Russian trolls and hac......一起来看看 《Cyberwar》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具