本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
1、写在前面
提升编译速度最快的是升级电脑配置,其次是接入Gradle Enterprice企业版,这两者简单粗暴,带来的效果也非常明显,但是这两者的成本却不一定是所有人都能接受的,特别是目前大家都在搞降本增效,所以想要靠外在力量来提升编译速度更是难上加难,遂,有了本文,通过Gradle配置最佳实践的方式来帮助大家低成本的提升编译速度。

钱不是问题,问题是没钱。
2、为什么要做编译优化
性能优化的三驾马车是启动、包大小和内存,这三个指标都跟用户的体验息息相关,所以编译优化就变成了重要不紧急
的事情,但编译优化也能带来诸多好处,主要有两点:
- 提升开发效率:开发者可以更快地编译和运行代码,这意味着对代码的变更可以更快的得到反馈,从而加速开发周期,提升个人和整个团队的生产力;
- 提升开发体验:减少了等待时间,提升个人和整个团队的幸福感和满意度;
影响摸鱼?干完不是可以摸的更舒畅吗...
3、影响编译速度的因素
主要有以下几个方面:
- 硬件性能:CPU、RAM等;
- 构建配置:缓存、增量编译等;
- 项目:项目的大小和复杂度,代码量、模块化、依赖管理等;
- 其他:网络速度,下载慢或者找不到等;
4、编译优化原则
所以,基于上面影响编译速度的因素来看,编译优化原则也可以推导为两个方面:
- 复用:提升复用率,不仅是代码的复用,也是编译产物的复用,不仅关注首次编译速度,也要关注二次编译速度;
- 更少:减少参与编译的文件,越少则处理越快,不仅是代码文件,还有资源文件;
5、最佳实践
ok,上面铺垫了这么多,下面给大家介绍一些实用的构建配置。
5.1、升级版本
5.1.1、升级Gradle
Gradle
作为一个构建工具,提升构建性能可以说是基础操作,基本每个大版本都会带来各种各样的性能提升,比如Gradle 6.6以后对配置阶段的提升,7.0以后对Kotlin编译的提升,8.0以后对增量编译的进一步提升等等,这些都是构建工具之外无法做到的,所以,如果你的Gradle还不是最新版本,有条件的话一定要试试。
虽然升级Gradle有一定的适配成本,但是如果不升,长此以往,技术负债只会越来越多。
Keeping up with Gradle version upgrades is low risk because the Gradle team ensures backwards compatibility between minor versions of Gradle.
官方还说是低风险,😆
5.1.2、升级Java
Gradle是运行在Java虚拟机上的,即JVM,Java性能的提升也会有利于Gradle。
5.1.3、升级Plugin
同Gradle,一般也都是跟随着Gradle升一波。
5.2、开启并行编译
Gradle默认一次只执行一个Task,即串行,那我们就可以通过配置让Gradle并行来执行Task,从而提升构建效率,缩短构建时间。
在gradle.properties
文件中添加:
ini
org.gradle.parallel=true
5.3、开启守护进程
开启守护进程之后,Gradle不仅可以更好的缓存构建信息,而且运行在后台,不用每次构建都去初始化然后再启动JVM了。
在gradle.properties
文件中添加:
ini
org.gradle.daemon=true
以上两个配置,在GradleX项目中测验,增量编译速度提升60%以上,实际以自己项目为准。
5.4、启用配置缓存
配置缓存是Gradle 6.6以后提供的能力。
当没有构建配置发生变化的时候,比如构建脚本,Gradle会直接跳过配置阶段,从而带来性能的提升。
构建配置主要是scripts和properties,一般业务开发也不会动这个,所以还是非常有用的。
ini
org.gradle.unsafe.configuration-cache=true
5.5、启用构建缓存
同一个Task的输入不变的情况下,Gradle直接去检索缓存中检索输出,就不用再次执行该Task了。
ini
org.gradle.caching=true
5.6、启用增量编译
Gradle 4.10及以上默认开启,如果是4.10以下的版本,可以加上如下代码手动开启。
build.gradle:
ini
tasks.withType(JavaCompile).configureEach {
options.incremental = true
}
关于Task的增量编译,请看这篇:【Gradle-7】Gradle构建核心之Task指南 - 掘金
5.7、增加JVM堆大小
默认是512m的堆大小,多数情况下并不够用,可以通过jvmargs
来调整内存堆大小。
ini
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
2048m=2g,也可以写成
ini
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
这个大小可以根据自己电脑配置来调整。
5.8、使用 JVM 并行垃圾回收器
通过配置Gradle所用的最佳JVM垃圾回收器,可以提升构建性能。-XX:+UseParallelGC
ini
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
5.9、增加Android Studio运行内存
上面提到增加Gradle的内存堆大小,AS的运行内存空间同样也可以调整。
调整AS内存的配置文件在AS > Contents > bin > studio.vmoptions
文件中

打开文件:
diff
-Xms256m
-Xmx1280m
-XX:ReservedCodeCacheSize=512m
-XX:+UseG1GC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
.....
这里主要有两个参数需要关注:
- -Xms256m:初始堆内存大小;
- -Xmx1280m:最大堆内存大小;
可以修改为:
diff
-Xms256m
-Xmx2048m
然后保存并重启AS生效即可。

Android Studio的运行内存占比可以在AS底部的工具栏上右键,把Memory Indicator
勾选上,然后在右下角就可以显示出来了。

5.10、优化依赖解析
5.10.1、删除无用的依赖
可以通过Gradle Lint Plugin来识别未使用的依赖项,然后删除,从而减少构建时间。
5.10.2、优化存储库顺序
Gradle在解析依赖时,默认按照配置声明的仓库地址顺序去搜索,即自上而下,所以为了减少搜索依赖项花费的时间,一般会按照项目中使用的依赖项归属的仓库来排列仓库地址顺序。
Android开发用的比较多的是google(),其次是mavenCentral(),所以这俩应该是在前面的,其他的看自己项目依赖情况排列。
scss
repositories {
google()
mavenCentral()
// others
}
5.10.3、优化依赖的下载速度
由于有些仓库的地址是在国外的,导致很多同学就经常遇到下载依赖很慢的情况,所以我们可以使用一些国内的镜像来提升下载速度。
rust
repositories {
// 阿里云仓库
maven { url 'https://maven.aliyun.com/repository/public/' }
google()
mavenCentral()
}
阿里云镜像整理:juejin.cn/post/690747...
5.10.4、优化依赖版本
避免使用动态版本
(2.+)和快照版本
(2-SNAPSHOT),这样可以避免Gradle在构建的时候去检索新版本,默认是24
小时检查一次。
比如微信的SDK,为了让大家及时更新就使用了动态版本:
arduino
api 'com.tencent.mm.opensdk:wechat-sdk-android:+'
看下SDK的介绍,直接使用具体的版本:
arduino
api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.23'
关于动态版本和快照版本的检索时效也可以通过以下配置来设置:
arduino
configurations.all {
// 动态版本缓存时效
resolutionStrategy.cacheDynamicVersionsFor(10, "minutes")
// 快照版本缓存时效
resolutionStrategy.cacheChangingModulesFor(4, "hours")
}
5.11、避免编译不必要的资源
避免编译和打包不测试的资源(例如,其他语言本地化和屏幕密度资源)。可以仅为"dev"变种的版本指定一个语言资源和屏幕密度,如下:
arduino
android {
productFlavors {
dev {
...
resourceConfigurations "en", "xxhdpi"
}
}
}
5.12、按需编译
道理同上,使用assembleDebug
或者assembleRelease
,来代替assemble
。
同理,还有debugImplementation
、abiFilters
。
以及按需依赖插件:
scss
if(debug){
apply xxx
}
5.13、禁用不需要的Task
还有Task也是同理,比如跳过Lint
和Test
相关的task, 以加速编译。
less
//跳过Lint和Test相关的task, 以加速编译
if (isDebug()) {
gradle.taskGraph.whenReady {
tasks.each { task ->
if (task.name.contains("Test") || task.name.contains("Lint")) {
task.enabled = false
}
}
}
}
5.14、将图片转换为WebP格式
WebP
是一种既可以提供有损压缩(像 JPEG 一样)也可以提供透明度(像 PNG 一样)的图片文件格式。与 JPEG或PNG相比,WebP格式可以提供更好的压缩效果。
减小图片文件大小可以加快构建速度(无需在构建时进行压缩),尤其是当应用使用大量图片资源时。
Android Studio中:
css
选中图片 > 右键 > Convert to WebP
5.15、停用PNG处理
即使不将PNG图片转换为WebP格式,仍然可以在每次构建应用时停用自动图片压缩,以加快构建速度。
arduino
android {
buildTypes {
release {
crunchPngs false
}
}
}
5.16、使用非传递R类
使用非传递 R 类可为具有多个模块的应用构建更快的 build。这样做有助于确保每个模块的 R 类仅包含对其自身资源的引用,而不会从其依赖项中提取引用,从而帮助防止资源重复。这样可以获得更快的 build,以及避免编译的相应优势。在Android Gradle 插件 8.0.0 及更高版本中的默认开启。
ini
android.nonTransitiveRClass=true
从 Android Studio Bumblebee 开始,新项目的非传递 R 类默认处于开启状态。 对于使用早期版本的 Android Studio 创建的项目,可以在 Refactor > Migrate to Non-transitive R Classes
,将项目更新为使用非传递 R 类。
5.17、停用Jetifier标志
由于大多数项目都直接使用 AndroidX 库,因此可以移除Jetifier
标志,以便获得更好的构建性能。
ini
android.enableJetifier=false
Jetifier
是把support包转成AndroidX的工具,现在基本上都已经适配AndroidX了,可以关掉,从而提升构建性能。
如果你是开启的,即android.enableJetifier=true
,可以使用Build Analyzer
工具来查看是否会有警告,即是否可以移除。
5.18、使用KSP代替kapt
kapt
是Kotlin注解处理工具,kapt的运行速度明显慢于Kotlin Symbol Processor (KSP)。官方:速度提升多达2倍。
5.18.1、启用KSP
project:
bash
plugins {
id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}
app:
bash
plugins {
id("com.google.devtools.ksp")
}
5.18.2、使用KSP
scss
dependencies {
// kapt
kapt("androidx.room:room-compiler:2.5.0")
// ksp
ksp("androidx.room:room-compiler:2.5.0")
}
就这么简单,然后移除kapt相关的配置即可。前提是使用的库已经适配了KSP,主流的基本已经适配。
5.18.3、K2
在kotlin 1.9.20
中K2已经处于beta版本了,会有进一步的性能提升。
尝鲜可以在gradle.properties中加上以下配置:
ini
kotlin.experimental.tryK2=true
kapt.use.k2=true
更多可查看:kotlin-k2-compiler。
5.19、去掉动态配置
每次编译之后会产生不同结果的配置。
5.19.1、动态的FileName
比如每次打包会把apk的名字加上当前构建时间,这样不仅会产生大量的文件,而且每次都要更新,影响编译速度。
arduino
applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "${variant.name}-${buildTime()}.apk"
}
}
static def buildTime() {
return new Date().format("MMdd_HHmm", TimeZone.default)
}
5.19.2、动态的VersionName
scss
defaultConfig {
versionCode $buildTime().toInteger()
versionName '1.0.$buildTime()'
}
static def buildTime() {
return new Date().format("MMdd_HHmm", TimeZone.default).toInteger()
}
道理同上,动态的versionCode
和versionName
也是不推荐的。
5.20、其他
5.20.1、开启离线模式
老版本可以在Settings>Build>Gradle
中把offline work
勾选上,或者在编译脚本里加上--offline
。
5.20.2、优化DexOptions
老版本配置:
arduino
android {
dexOptions {
// 使用增量模式构建
incremental true
// 最大堆内存
javaMaxHeapSize "4g"
// 是否支持大工程模式
jumboMode = true
// 预编译
preDexLibraries = true
// 线程数
threadCount = 8
// 进程数
maxProcessCount 4
}
}
5.20.3、模块化
一般项目中使用了模块化的这种架构设计,都会做源码依赖和AAR依赖的切换,来提升构建速度,主要是减少了参与编译的代码,再加上缓存,所以提升效果明显。这个后面单独写一篇介绍下。
5.20.4、构建分析
使用构建分析来解决项目中的构建耗时点,以及不合理配置,参考:【Gradle-10】不可忽视的构建分析。

6、总结
本文先是介绍了为什么要做编译优化,然后分析了影响编译速度的因素有哪些,从最少、复用的构建原则入手,详细的为大家介绍了一些低成本且实用的最佳实践指南。如果你还没有优化过,可以实操起来了~
如果本文对你有一点点帮助,请不要吝啬你的点赞和关注~