前言
最近Compose
已经正式发布了1.0
版本,这说明谷歌认为Compose
已经可以用于正式生产环境了
那么相比传统的XML
,Compose
的性能到底怎么样呢?
本文主要从构建性能与运行时两个方面来分析Compose
的性能,数据主要来源于:Jetpack Compose — Before and after(https://medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f) 与 Measuring Render Performance with Jetpack Compose (https://engineering.premise.com/measuring-render-performance-with-jetpack-compose-c0bf5814933)
构建性能
Compose
构建性能主要以 tivi (https://github.com/chrisbanes/tivi/)为例来进行说明Tivi
是一个开源的电影App
,原本基于Fragment
与XML
构建,同时还使用了DataBinding
等使用了注解处理器的框架
后来迁移到使用Compose
构建UI
,迁移过程分为两步
第一步:迁移到
Navigation
与Fragment
,每个Fragment
的UI
则由Compose
构建第二步:移除
Fragment
,完全基于Compose
实现UI
下面我们就对Pre-Compose
,Fragments + Compose
,Entirely Compose
三个阶段的性能进行分析对比
1、APK
体积
包体积是我们经常关注的性能指标之一,我们一起看下3个阶段的包体积对比
可以看出,Tivi
的 APK
大小缩减了 46%
,从 4.49MB
缩减到 2.39MB
,同时方法数也减少了17%
值得注意的是,在刚开始在应用中采用Compose
时,有时您会发现APK
大小反而变大了
这是因为迁移没有完成,老的依赖没有完成移除,而新的依赖已经添加了,导致APK
体积变大
而在项目完全迁移到Compose
后,APK
大小会减少,并且优于原始指标。
2、代码行数
我们知道在比较软件项目时,代码行数并不是一个特别有用的统计数据,但它确实提供了对事物如何变化的一个观察指标。
我们使用cloc工具来计算代码行数
cloc . --exclude-dir=build,.idea,schemas
结果如下图所示:
可以看出,在迁移到
Compose
后,毫无意外的,XML
代码行减少了76%
有趣的是kotlin
代码同样减少了,可能是因为我们可以减少很多模板代码,同时也可以移除之前写的一些View Helper
代码
3、构建速度
随着项目的不断变大,构建速度是开发人员越来越关心的一个指标。
在开始重构之前,我们知道,删除大量的注解处理器会有助于提高构建速度,但我们不确定会有多少。
我们运行以下命令5次,然后取平均值
./gradlew --profile --offline --rerun-tasks --max-workers=4 assembleDebug
结果如下
这里考虑的是调试构建时间,您在开发期间会更关注此时间。
在迁移到Compose
前,Tivi
的平均构建时间为 108.71
秒。
在完全迁移到 Compose
后,平均构建时间缩短至 76.96
秒!构建时间缩短了 29%
。
构建时间能缩短这么多,当然不仅仅是Compose
的功劳,在很大程度上受两个因素的影响:
一个是移除了使用注解处理器的
DataBinding
和Epoxy
另一个是
Hilt
在AGP 7.0
中的运行速度更快。
运行时性能
上面我们介绍了Compose
在构建时的性能,下面来看下Compose
在运行时渲染的性能怎么样
1、分析前的准备
使用Compose
时,可能有多种影响性能的指标
如果我们完全在
Compose
中构建UI
会怎样?如果我们对复杂视图使用
Compose
(例如用LazyColumn
替换RecyclerViews
),但根布局仍然添加在XML
中如果我们使用
Compose
替换页面中一个个元素,而不是整个页面,会怎么样?是否可调试和
R8
编译器对性能的影响有多大?
为了开始回答这些问题,我们构建了一个简单的测试程序。
在第一个版本中,我们添加了一个包含50个元素的列表(其中实际绘制了大约 12 个)。该列表包括一个单选按钮和一些随机文本。
为了测试各种选项的影响,我们添加以下4种配置,以下4种都是开启了R8
同时关闭了debug
纯Compose
一个
XML
中,只带有一个ComposeView
,具体布局写在Compose
中XML
中只包含一个RecyclerView
,但是RecyclerView
的每一项是一个ComposeView
纯
XML
同时为了测试build type
对性能的影响,也添加了以下3种配置
纯
Compose
,关闭R8
并打开debug
纯
Compose
,关闭R8
并关闭debug
纯
XML
,关闭R8
并打开debug
2、如何定义性能?
Compose
运行时性能,我们一般理解的就是页面启动到用户看到内容的时间
因此下面几个时机对我们比较重要
Activity
启动时间,即onCreate
Activity
启动完成时间,即onResume
Activity
渲染绘制完成时间,即用户看到内容的时间
onCreate
与onResume
的时机很容易掌握,重写系统方法即可,但如何获得Activity
完全绘制的时间呢?
我们可以给页面根View
添加一个ViewTreeObserver
,然后记录最后一次onDraw
调用的时间
使用Profile
查看上面说的过程,如下分别为使用XML
渲染与使用Compose
渲染的具体过程,即从OnCreate
到调用最后一次onDraw
的过程
3、渲染性能分析
知道了如何定义性能,我们就可以开始测试了
每次测试都在几台设备上运行,包括最近的旗舰、没有
Google Play
服务的设备和一些廉价手机。每次测试在同一台手机上都会运行10次,因此我们不仅可以获取首次渲染时间,也可以获取二次渲染时间
测试
Compose
版本为1.0.0
我们根据上面定义的配置,重复跑了多次,得到了一些数据,感兴趣的同学可以直接查看所有数据
分析结果如上图所示,我们可以得出一些结论
R8
和是否可调试对Jetpack Compose
渲染时间产生了显着影响。在每次实验中,禁用R8
和启用可调试性的构建所花费的时间是没有它们的构建的两倍多。在我们最慢的设备上,R8
将渲染速度加快了半秒以上,而禁用debug
又使渲染速度加快了半秒。XML
中只包含一个ComposeView
的渲染时间,跟纯Compose
的耗时差不多RecyclerView
中包含多个ComposeView
是最慢的。这并不奇怪,在XML
中使用ComposeView
是有成本的,所以页面中使用的ComposeView
越少越好。XML
在呈现方面比Compose
更快。没有办法解决这个问题,在每种情况下,Compose
的渲染时间比XML
长约 33%。第一次启动总是比后续启动花费更长的时间来渲染。如果您查看完整的数据,第一个页面的渲染时间几乎是后续的两倍。
比较让我惊讶的是,尽管Compose
没有了将XML
转化成View
的IO
操作,测量过程也因为固有特性测量提高了测量效率,但性能仍然比XML
要差
不过,根据Leland Richardson
的说法,当从Google Play
安装应用程序时,由于捆绑的AOT
编译,Compose
在启动时渲染会更快,从而进一步缩小了与XML
的差距
总结
经过上面对Compose
性能方面的分析,总结如下
如果完全迁移到
Compose
,在包体积,代码行数,编译速度等方面应该会有比较大的改善如果处于迁移中的阶段,可能因为旧的依赖没有去除,而已经引入了新的依赖,反而导致包体积等变大
尽管没有了
XML
转换的IO
操作,测量过程也通过固有特性测量进行了优化,Compose
的渲染性能比起XML
仍然有一定差距尽管目前
Compose
在性能方面略有欠缺(在大多数设备上仅超过一两帧),但由于其在开发人员生产力、代码重用和声明式UI
的强大特性等方面的优势,Compose
仍被推荐使用
作者:RicardoMJiang
链接:https://juejin.cn/post/7008522702835154980
关注我获取更多知识或者投稿