编译优化之Gradle最佳配置实践

1、写在前面

提升编译速度最快的是升级电脑配置,其次是接入Gradle Enterprice企业版,这两者简单粗暴,带来的效果也非常明显,但是这两者的成本却不一定是所有人都能接受的,特别是目前大家都在搞降本增效,所以想要靠外在力量来提升编译速度更是难上加难,遂,有了本文,通过Gradle配置最佳实践的方式来帮助大家低成本的提升编译速度。

2、为什么要做编译优化

性能优化的三驾马车是启动、包大小和内存,这三个指标都跟用户的体验息息相关,所以编译优化就变成了重要不紧急的事情,但编译优化也能带来诸多好处,主要有两点:

  1. 提升开发效率:开发者可以更快地编译和运行代码,这意味着对代码的变更可以更快的得到反馈,从而加速开发周期,提升个人和整个团队的生产力;
  2. 提升开发体验:减少了等待时间,提升个人和整个团队的幸福感和满意度;

影响摸鱼?干完不是可以摸的更舒畅吗...

3、影响编译速度的因素

主要有以下几个方面:

  • 硬件性能:CPU、RAM等;
  • 构建配置:缓存、增量编译等;
  • 项目:项目的大小和复杂度,代码量、模块化、依赖管理等;
  • 其他:网络速度,下载慢或者找不到等;

4、编译优化原则

所以,基于上面影响编译速度的因素来看,编译优化原则也可以推导为两个方面:

  1. 复用:提升复用率,不仅是代码的复用,也是编译产物的复用,不仅关注首次编译速度,也要关注二次编译速度;
  2. 更少:减少参与编译的文件,越少则处理越快,不仅是代码文件,还有资源文件;

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文件中添加:

org.gradle.parallel=true

5.3、开启守护进程

开启守护进程之后,Gradle不仅可以更好的缓存构建信息,而且运行在后台,不用每次构建都去初始化然后再启动JVM了。

在gradle.properties文件中添加:

org.gradle.daemon=true

以上两个配置,在GradleX项目中测验,增量编译速度提升60%以上,实际以自己项目为准。

https://github.com/yechaoa/GradleX

5.4、启用配置缓存

配置缓存是Gradle 6.6以后提供的能力。

当没有构建配置发生变化的时候,比如构建脚本,Gradle会直接跳过配置阶段,从而带来性能的提升。

构建配置主要是scripts和properties,一般业务开发也不会动这个,所以还是非常有用的。

org.gradle.unsafe.configuration-cache=true

5.5、启用构建缓存

同一个Task的输入不变的情况下,Gradle直接去检索缓存中检索输出,就不用再次执行该Task了。

org.gradle.caching=true

5.6、启用增量编译

Gradle 4.10及以上默认开启,如果是4.10以下的版本,可以加上如下代码手动开启。

build.gradle:

tasks.withType(JavaCompile).configureEach {
    options.incremental = true
}

关于Task的增量编译,请看这篇:【Gradle-7】Gradle构建核心之Task指南 - 掘金

https://juejin.cn/post/7248207744087277605#heading-32

5.7、增加JVM堆大小

默认是512m的堆大小,多数情况下并不够用,可以通过jvmargs来调整内存堆大小。

org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8

2048m=2g,也可以写成:

org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8

这个大小可以根据自己电脑配置来调整。

5.8、使用 JVM 并行垃圾回收器

通过配置Gradle所用的最佳JVM垃圾回收器,可以提升构建性能。-XX:+UseParallelGC

org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC

5.9、增加Android Studio运行内存

上面提到增加Gradle的内存堆大小,AS的运行内存空间同样也可以调整。

调整AS内存的配置文件在AS > Contents > bin > studio.vmoptions文件中。

图片

打开文件:

-Xms256m
-Xmx1280m
-XX:ReservedCodeCacheSize=512m
-XX:+UseG1GC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
.....

这里主要有两个参数需要关注:

  • -Xms256m:初始堆内存大小。

  • -Xmx1280m:最大堆内存大小。

可以修改为:

-Xms256m
-Xmx2048m

然后保存并重启AS生效即可。

图片

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

5.10、优化依赖解析

5.10.1、删除无用的依赖

可以通过Gradle Lint Plugin来识别未使用的依赖项,然后删除,从而减少构建时间。

5.10.2、优化存储库顺序

Gradle在解析依赖时,默认按照配置声明的仓库地址顺序去搜索,即自上而下,所以为了减少搜索依赖项花费的时间,一般会按照项目中使用的依赖项归属的仓库来排列仓库地址顺序。

Android开发用的比较多的是google(),其次是mavenCentral(),所以这俩应该是在前面的,其他的看自己项目依赖情况排列。

repositories {
    google()
    mavenCentral()
    // others
}

5.10.3、优化依赖的下载速度

由于有些仓库的地址是在国外的,导致很多同学就经常遇到下载依赖很慢的情况,所以我们可以使用一些国内的镜像来提升下载速度。

repositories {
    // 阿里云仓库
    maven { url 'https://maven.aliyun.com/repository/public/' }
    google()
    mavenCentral()
}

阿里云镜像整理:

https://juejin.cn/post/6907479665305190408

5.10.4、优化依赖版本

避免使用动态版本(2.+)和快照版本(2-SNAPSHOT),这样可以避免Gradle在构建的时候去检索新版本,默认是24小时检查一次。

比如微信的SDK,为了让大家及时更新就使用了动态版本:

api 'com.tencent.mm.opensdk:wechat-sdk-android:+'

看下SDK的介绍,直接使用具体的版本:

api 'com.tencent.mm.opensdk:wechat-sdk-android:6.8.23'

关于动态版本和快照版本的检索时效也可以通过以下配置来设置:

configurations.all {
        // 动态版本缓存时效
    resolutionStrategy.cacheDynamicVersionsFor(10, "minutes")
    // 快照版本缓存时效
    resolutionStrategy.cacheChangingModulesFor(4, "hours")
}

5.11、避免编译不必要的资源

避免编译和打包不测试的资源(例如,其他语言本地化和屏幕密度资源)。可以仅为“dev”变种的版本指定一个语言资源和屏幕密度,如下:

android {
    productFlavors {
        dev {
            ...
            resourceConfigurations "en", "xxhdpi"
        }
    }
}

5.12、按需编译

道理同上,使用assembleDebug或者assembleRelease,来代替assemble。

同理,还有debugImplementation、abiFilters。

以及按需依赖插件:

if(debug){
      apply xxx
}

5.13、禁用不需要的Task

还有Task也是同理,比如跳过Lint和Test相关的task, 以加速编译。

 //跳过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中:

选中图片 > 右键 > Convert to WebP

5.15、停用PNG处理

即使不将PNG图片转换为WebP格式,仍然可以在每次构建应用时停用自动图片压缩,以加快构建速度。

android {
    buildTypes {
        release {
            crunchPngs false
        }
    }
}

5.16、使用非传递R类

使用非传递 R 类可为具有多个模块的应用构建更快的 build。这样做有助于确保每个模块的 R 类仅包含对其自身资源的引用,而不会从其依赖项中提取引用,从而帮助防止资源重复。这样可以获得更快的 build,以及避免编译的相应优势。在Android Gradle 插件 8.0.0 及更高版本中的默认开启。

android.nonTransitiveRClass=true

从 Android Studio Bumblebee 开始,新项目的非传递 R 类默认处于开启状态。对于使用早期版本的 Android Studio 创建的项目,可以在 Refactor > Migrate to Non-transitive R Classes,将项目更新为使用非传递 R 类。

5.17、停用Jetifier标志

由于大多数项目都直接使用 AndroidX 库,因此可以移除Jetifier标志,以便获得更好的构建性能。

android.enableJetifier=false

Jetifier是把support包转成AndroidX的工具,现在基本上都已经适配AndroidX了,可以关掉,从而提升构建性能。

如果你是开启的,即android.enableJetifier=true,可以使用Build Analyzer工具来查看是否会有警告,即是否可以移除。

5.18、使用KSP代替kapt

kapt是Kotlin注解处理工具,kapt的运行速度明显慢于Kotlin Symbol Processor (KSP)。官方:速度提升多达2倍。

https://android-developers.googleblog.com/2021/09/accelerated-kotlin-build-times-with.html

5.18.1、启用KSP

project:

plugins {
    id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}

app:

plugins {
    id("com.google.devtools.ksp")
}

5.18.2、使用KSP

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中加上以下配置:

kotlin.experimental.tryK2=true
kapt.use.k2=true

更多可查看:kotlin-k2-compiler

5.19、去掉动态配置

每次编译之后会产生不同结果的配置。

5.19.1、动态的FileName

比如每次打包会把apk的名字加上当前构建时间,这样不仅会产生大量的文件,而且每次都要更新,影响编译速度。

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

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

老版本配置:

android {
    dexOptions {
        // 使用增量模式构建
        incremental true
        // 最大堆内存
        javaMaxHeapSize "4g"
        // 是否支持大工程模式
        jumboMode = true
        // 预编译
        preDexLibraries = true
        // 线程数
        threadCount = 8
        // 进程数
        maxProcessCount 4
    }
}

5.20.3、模块化

一般项目中使用了模块化的这种架构设计,都会做源码依赖和AAR依赖的切换,来提升构建速度,主要是减少了参与编译的代码,再加上缓存,所以提升效果明显。这个后面单独写一篇介绍下。

5.20.4、构建分析

使用构建分析来解决项目中的构建耗时点,以及不合理配置,参考:【Gradle-10】不可忽视的构建分析

6、总结

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

如果本文对你有一点点帮助,请不要吝啬你的点赞和关注~

7、GitHub

github.com/yechaoa/Gra…

8、相关文档

作者:yechaoa
链接:https://juejin.cn/post/7344625554529730600
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值