文章

04.Gradle缓存

04.Gradle缓存

04.Gradle 缓存

Gradle 缓存

2ks82

Gradle 内存缓存

Gradle 内存缓存主要是通过 Gradle Daemon 进程 (即守护进程) 实现的。
开启 Gradle Daemon(Gradle 3.0 之后守护进程默认开启)
在 gradle.properties 中设置 org.gradle.daemon=false
Gradle Daemon 优势

  1. 多次构建之间重用,只初始化一次
    Gradle Daemon 是一个长期存在的进程,在多次构建之间,它将空闲地等待下一个构建;多个构建只需要初始化一次,而不是每个构建都需要初始化
  2. JVM 的 JIT 优化,构建次数越多构建越快
    JVM 有运行时代码优化 (JIT),在代码执行过程中逐渐优化;根据 HotSpot 实现表明,JIT 优化通常需要 5~10 次构建才能稳定,因此 Daemon 的第一次构建和第十次构建之间的构建时间差异可能非常大
  3. 多次构建之间可以对构建脚本、构建插件、构建数据等进行内存缓存,以加快构建速度
  4. 可以检测两次构建之间的文件系统的变化,并计算出需要重新构建的文件,方便增量构建

Gradle 项目缓存

项目缓存主要存储在根目录的 .gradle 和各个 module 的 build 目录,其中 configuration-cache 存储在 .gradle 目录,而各个 task 的执行结果存储在 build 目录

configuration-cache

缓存配置阶段的结果
开启 configuration-cache:

1
2
3
# configuration cache
org.gradle.unsafe.configuration-cache=true
org.gradle.unsafe.configuration-cache-problems=warn

configuration-cache 适配:
不要在 task 执行阶段调用外部不可序列化的对象(如 Project 和 Variant)

1
2
3
4
5
6
7
8
android {
    applicationVariants.all { variant ->
        def mergeAssetTask = variant.getMergeAssetsProvider().get()
        mergeAssetTask.doLast {
           project.logger(variant.buildType.name)
        }
    }
}

如上所示,在 doLast 阶段调用了 project 与 variant 对象,这两个对象是在配置阶段生成的,但是又无法序列化,因此这段代码无法适配 Configuration Cache,需要修改如下:

1
2
3
4
5
6
7
8
9
android {
    applicationVariants.all { variant ->
    	def buildTypeName = variant.buildType.name
        def mergeAssetTask = variant.getMergeAssetsProvider().get()
        mergeAssetTask.doLast {
           logger(buildTypeName)
        }
    }
}

task cache

Gradle 本机缓存

Gradle 本机缓存即 GRADLE USER HOME 路径 (~/.gradle) 下的 cache 目录
缓存更换目录:
新建 GRADLE_USER_HOME 环境变量,重启计算机即可

Build Cache

Build Cache 默认未开启

  1. 命令行添加 --build-cache,Gradle 将只为此次构建使用 Build Cache
  2. gradle.properties 添加 org.gradle.caching=true,Gradle 将尝试为所有构建重用以前的构建的输出,除非通过 --no-build-cache 禁用了

可缓存 Task

build cache 命中时,该 Task 会被标记为 FROM-CACHE

本地依赖缓存

所有远程下载的 aar 都在 cache/modules-2 目录下,这些 aar 可以在本地所有项目间共享,通过这种方式可以有效避免不同项目之间相同依赖的反复下载
我们应该尽量使用稳定依赖,避免使用 动态 (Dynamic) **或者快照 (SNAPSHOT) **版本依赖
使用稳定依赖版本,当下载成功后,后续再有引用该依赖的地方都可以从缓存读取, 避免缓慢的网络下载。而动态和快照这两种版本引用会迫使 Gradle 链接远程仓库检查是否有更新的依赖可用, 如果有则下载后缓存到本地.默认情况下,这种缓存有效期为 24小时. 可以通过以下方式调整缓存有效期:

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

动态版本和快照版本会影响编译速度, 尤其在网络状况不佳的情况下以及该依赖仅仅出现在内部 repo 的情况下. 因为 Gradle 会串行查询所有 repo, 直到找到该依赖才会下载并缓存. 然而这两种依赖方式失效后就需要重新查询和下载。
同时这动态版本与快照版本也会导致 Configuration Cache 失效,因此应该尽量使用稳定版本

Gradle 远程缓存

镜像 repo

Gradle 下载 aar 有时非常耗时,一种常见的操作时添加镜像 repo,比如公开的阿里镜像等。或者部署公司内部的镜像 repo,以加快在公司网络的访问速度,也是很常见的操作。

关于 Gradle 仓库配置还有一些小技巧:Gradle 在查找远程依赖的时候, 会串行查询所有 repo 中的 maven 地址, 直到找到可用的 aar 后下载. 因此把最快和最高命中率的仓库放在前面, 会有效减少 configuration 阶段所需的时间.
除了顺序以外, 并不是所有的仓库都提供所有的依赖, 尤其是有些公司会将业务 aar 放在内部搭建的仓库上. 这种情况下如果盲目增加 repository 会让 Configuration 时间变得难以接受. 我们通常需要将内部仓库放在最前, 同时明确指定哪些依赖可以去这里下载:

1
2
3
4
5
6
7
8
9
repositories {
    maven {
        url = uri("http://repo.mycompany.com/maven2")
        content {
            includeGroup("com.test")
        }
    }
    // ...
}

远程 Build Cache

Build Cache 不仅可以保存在本地 ($GRADLE_USER_HOME/caches), 也可以使用网络路径。在 settings.gradle 中加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// settings.gradle.kts
buildCache {
    local<DirectoryBuildCache> {
        directory = File(rootDir, "build-cache")

        // 编译结果是否同步到本地缓存. local cache 默认 true
        push = true

        // 无用缓存清理时间
        removeUnusedEntriesAfterDays = 30
    }

    remote<HttpBuildCache> {
        url = uri("https://example.com:8123/cache/")

        // 编译结果是否同步到远程缓存服务器. remote cache 默认 false
        push = false

        credentials {
            username = "build-cache-user"
            password = "some-complicated-password"
        }

        // 如果遇到 https 不授信问题, 可以关闭校验. 默认 false
        isAllowUntrustedServer = true
    }
}

通常我们在 CI 编译脚本中 push = true,而开发人员的机器上 push = false 避免缓存被污染。
要实现 Build Cache 在多个机器上的共享,需要一个缓存服务器,官方提供了两种方式搭建缓存服务器: Docker 镜像和 jar 包。
远程 Build Cache 应该也是一个可行的方案,试想如果我们有一个高性能的打包机,当每次打码提交时,都自动编译生成 Build Cache,那么开发人员都可以高效地复用同一份 Build Cache,以加快编译速度,而不是每次更新代码都需要在本机重新编译。

修改 GRADLE 缓存目录

定义 GRADLE_USER_HOME 环境变量,值为新的 Gradle 缓存目录

本文由作者按照 CC BY 4.0 进行授权