[toc]
一 前言
编译速度优化是大型项目中提升研发效率的重要环节,大部分人都经历过改动一行代码,需等待30秒以上来重新查看运行效果的尴尬局面,本文将从问题认知角度来尝试展开。
要优化编译速度,得知道这么几个最重要的事:
- 编译流程是什么样的?从点击as的run到代码重新运行经历了哪些流程?
- 编译耗时输出,如何打印流程中的耗时情况?
- 单个耗时任务中包含了什么?怎么优化?优化效果如何?
二.编译流程分析
google官方提供的编译流程说明图如下:
江湖上还流传着二副更加完整的编译流程图:
从流程上看,除了官方给的比较笼统外,另外两副流程基本一致,只是详略不同。
- aapt 打包资源文件,产生R.java ,resource.arsc , res,manifest.xml
- aidl 产生java接口文件
- javac 编译R.java,Aidl产生的java文件以及工程中用到的源码产生.class文件
- R8混淆和代码压缩.class文件,经过d8 生成.dex文件
- apk builder 将资源包,.dex打包生成apk文件
- apksigner /jarSigner 会对apk进行签名
三.编译耗时输出
3.1 命令行查看编译耗时
./gradlew clean app:assembleDebug --profile
# 输出结果如下
Description Duration
Total Build Time 2m13.22s
Startup 2.711s
Settings and buildSrc 0.794s
Loading Projects 0.090s
Configuring Projects 8.249s
Artifact Transforms 27.957s
Task Execution 2m13.36s
如果想看更加详细的分析说明,可以增加--scan 属性 它会自动发送到scans.gradle.com网址下
./gradlew clean app:assembleDebug --scan
3.2 android studio 查看编译耗时
-
从菜单栏中依次点击 Build > Build Bundle(s) / APK(s) > Build Bundle(s) 或 Build > Build Bundle(s) / APK(s) > Build APK(s)
-
从菜单栏中依次选择 View > Tool Windows > Build,打开 Build 窗口
-
等构建结束之后,查看 Build Analyzer标签
四. 编译流程分析
整体上看,编译流程满足gradle的三大流程
graph LR
init-->configure
configure-->execute
占比来看,
- init包含 Startup,setting ,loading 耗时3.6s
- configure 耗时 8.2s
- 各种transforms耗时 27.85s
- 任务执行耗时总耗时2分13s ,这个包含了前面三个耗时。
五.耗时优化分析
从上面的输出可以看出,任务执行耗时最长,我们切换到timeline视图,选择按时长排序,就可以筛选出来执行任务的耗时排序。
5.1 maven私有仓库镜像
建立私有仓库镜像常用的mavencenter,jcenter,google和自己公司的maven,减少多次请求查询网络时间。对于首次编译有效,实际优化效果,视情况而定。通过分析发现私服仓库包含了以下四个仓库,因此可以去掉。
// mavenCentral()
// google()
// jcenter()
// maven { url "https://jitpack.io" }
5.2 模块化处理
将依赖的module改为aar,整体耗时从2分18秒,降低到1分。提升130%,这里比较麻烦的在于各个模块抽象之后的依赖关系梳理。
# 改造前
implementation project(':umeng')
implementation project(':user')
implementation project(':oss')
implementation project(':feedbacks')
implementation project(':videoedit')
implementation project(':ox')
implementation project(':ark')
implementation project(':hyvideo')
# 改造后
implementation 'com.hch:ark:2.0.2'
implementation 'com.hch:ox:2.0.1'
implementation 'com.hch:hyvideo:2.0.1'
implementation 'com.hch:umeng:2.0.0'
implementation 'com.hch:user:2.0.1'
implementation 'com.hch:oss:2.0.1'
implementation 'com.hch:feedbacks:2.0.1'
implementation 'com.hch:videoedit:2.0.0'
- 优化前
- 优化后
5.3 冗余模块移除
分析依赖关系发现dlna,webserver两个模块经过迭代之后,仅剩下少量几个工具类被引用,而实际包含了大量的冗余代码,提取工具类后,移除了两个模块。
收益3s左右
5.4 开启全局缓存
在gradle.properties中添加全局缓存,这个缓存可以跨机器生效,并且在执行clean任务的情况下依旧会生效。提升效果非常明显 从42s下降到20s左右。
org.gradle.caching=true
它与增量编译的优先级是,增量优先 ,其次是全局缓存
> Task :app:dexBuilderDebug UP-TO-DATE
> Task :app:mergeExtDexDebug UP-TO-DATE
> Task :app:mergeLibDexDebug UP-TO-DATE
UP-TO-DATE表示模块没有变化,我们通过Build-clean之后再次运行
> Task :app:dexBuilderDebug FROM-CACHE
> Task :app:mergeExtDexDebug FROM-CACHE
> Task :app:mergeLibDexDebug FROM-CACHE
FROM-CACHE表示是从缓存中读取编译结果。
这部分提升,如果是没有修改的情况下,再次运行只需要5秒,有修改内容时,clean后编译大概是40ms左右。
关于cache的说明:
- cache有效期最新一次编译后的7天内
- 本地cache目录$GRADLE_USER_HOME/caches/build-cache-1
- 使用cachec会有校验与打包的过程,总体来说比直接编译速度要快。
- 修改代码会导致最慢的流程dexBuilder和mergeDex缓存失效
5.5 升级AGP版本
AGP版本在迭代过程中一直有提升构建的性能,因此在可能的情况下尽量使用新的AGP版本。
5.6 并行编译
org.gradle.parallel=true
由于我们的module都拆解成了aar,因此实际上并行编译的优化效果并不明显。它比较适合在有多个互相不依赖的模块中提升编译速度。例如:部分模块是aar,另外部分是源码依赖的形式。
5.7 开启守护进程
org.gradle.configureondemand=true
当gradle进程没有启动时,会节约大概4秒时间
5.8 加大缓存
org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC
带来收益较小,主要是影响垃圾回收的时间,平均提升大概3s。
5.9 png优化处理
这部分对dexBuilder任务的构建时间优化比较明显,减小图片体积,并且提升编译速度。
- 将png改为webp格式
- 禁用png的优化
buildTypes {
debug {
crunchPngs false
}
release {
crunchPngs false
}
}
实际效果,编译速度在本项目中提升4S左右
5.9 transform优化
transform最大的问题在于它是串行处理的,每个transform的输出会成为下一个transform的输入,因此这里重点的优化手段都是尽可能的将多个transform的处理逻辑合并,减少全量扫描的次数。基本优化思路包括:
- 合并transform,尽量统一处理
- 减少多种transform库的引入,统一使用一种。
六.总结
- 在可能的情况下尽量升级使用新的AGP.
- 采用私有仓库镜像能大幅降低首次拉取网络资源的时间
- 模块化转aar依赖,能大幅提升构建效率,项目中提升120%以上,从2分10秒降低到1分5秒
- 开启全局缓存,能够大幅提升构建效率。项目中提升40%以上,从1分5秒降低到40s,在无修改clean情况下只需17s
- png转webp,禁用png优化,降低耗时4s。
- 冗余模块移除提升幅度与模块大小有关,项目中降低3s耗时
- 增加vm内存效果不明显
- 并行编译效果不明显
- 开启守护进程,首次启动降低4秒耗时
整体来看收益 模块化 > 全局缓存 > png优化 > 其他配置 最终的配置如下:
# build opt
org.gradle.caching=true
org.gradle.parallel=true
# 开启守护进程
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC
# 在app级的.gradle文件中添加
buildTypes {
debug {
crunchPngs false
}
release {
crunchPngs false
}
}