查看原文
其他

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

yechaoa 鸿洋
2024-08-24

本文作者


作者:yechaoa

链接:

https://juejin.cn/post/7344625554529730600

本文由作者授权发布。


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来识别未使用的依赖项,然后删除,从而减少构建时间。

https://github.com/nebula-plugins/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。
同理,还有debugImplementationabiFilters
以及按需依赖插件:
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

https://kotlinlang.org/docs/whatsnew1920.html#how-to-enable-the-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】不可忽视的构建分析

https://juejin.cn/post/7282150745164005432



6总结


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

GitHub

https://github.com/yechaoa/GradleX

相关文档

Improve the Performance of Gradle Builds

https://docs.gradle.org/current/userguide/performance.html

What's new in Gradle 7.0

https://gradle.org/whats-new/gradle-7/

What's new in Gradle 8.0

https://gradle.org/whats-new/gradle-8/

Configuration cache

https://docs.gradle.org/8.0.2/userguide/configuration_cache.html

优化您的构建速度

https://developer.android.com/studio/build/optimize-your-build

从 kapt 迁移到 KSP

https://developer.android.com/build/migrate-to-ksp?hl=zh-cn#kts

An In-depth Look at Gradle's Approach to Faster Compilation

https://blog.gradle.org/our-approach-to-faster-compilation



最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

Android RecyclerView宫格拖拽效果实现
Uni-app开发入门:跨平台应用开发指南
Android 线程池周期性任务踩坑探究


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存