专栏首页天天P图攻城狮例说 Constraint Layout(三)—— 性能测评

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

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

本文分享自微信公众号 - 天天P图攻城狮(ttpic_dev)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • iOS基于GPUImage的图像形变设计(复杂形变部分)

    在上一部分,我们介绍了两种简单形变的GPUImage实现方式,包括自定义FragmentShader,和自定义顶点数组。这一部分,我们将介绍更为复杂的一些图像形...

    天天P图攻城狮
  • JPEG文件格式解析(一) Exif 与 JFIF

    而我们通常说的JPEG指的是以JPEG格式压缩的图片(即文件后缀为.jpeg .jpe )。经过JPEG重新编码的图片,文件压缩率可以达到90%以上,而且图片本...

    天天P图攻城狮
  • 由生成模型到domain迁移:GAN、CGAN、StarGAN、CycleGAN、AsymmetricCycleGAN

    最近看一篇CVPR2018文章PairedCycleGAN: Asymmetric Style Transfer for Applying and Remov...

    天天P图攻城狮
  • 邓侃:谷歌Talk to books引爆搜索方式革命

    ?---- 【新智元导读】昨天,新智元介绍了谷歌的全新搜索工具“Talk to Books”,基于自然语言文本理解,用户能够凭语义而非关键词来实现搜索功能。谷歌...

    新智元
  • 300亿条出租车数据里的五大秘密:上海8点13分最堵,司机凌晨喜欢把车停靠在…

    上海强生出租车公司的出租车每隔10秒钟会自动向总部的服务器发送一条数据,记录自己所在的经纬度、车速、车内是否有人、行驶方向等信息。2015年上海政府公开了4月一...

    华章科技
  • 从零开始用nodejs写一个区块链 原

    github地址 https://github.com/lilugirl/corechain

    lilugirl
  • 在CentOS7上通过Yum Repository安装MySQL5.7.21

    版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

    耕耘实录
  • ThreadLocal简单学习使用

    感觉threadLocal的一个好处是 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。(from https://ba...

  • Hadoop总结篇之四---底层通信是怎么做到的

    上一篇介绍了一个job的提交过程。期间多次提到通信协议。那么协议是什么? 协议其实就是通信的双方所遵守的一套规范,这套规范规定了通信时传输的数据的固定的格式。 ...

    小端
  • 从0到57万,这个小程序是这样做到的!

    以上例子都表明:营销活动设置的好,小程序才能快速吸引更多新用户。为了给广大客户提供更多案例,2018年6月29日下午,场景录培训部在腾讯众创空间开展了线下培训交...

    场景录小程序

扫码关注云+社区

领取腾讯云代金券