如何启用 Gradle 的 Daemon、Parallel 和 Configuration-on-Demand 优化?

解读

在国内 Android 面试中,Gradle 构建速度是“必考题”。
一面往往先问“你平时怎么加快编译”,答出“开并行、开守护进程”只能算及格;
二面会追问“原理、版本差异、CI 怎么配、为什么有时反而更慢”,如果答不上来就会被判定为“只会背博客”。
因此,回答时要给出“命令行-IDE-CI 全场景配置 + 版本差异 + 踩坑经验”,让面试官确信你在 200+ 模块的大型项目里真刀真枪用过。

知识点

  1. Gradle Daemon:长期驻留的 JVM 进程,复用 JIT 缓存与内存,避免每次启动新 JVM。
  2. Parallel:多模块并行执行 task,要求模块间无隐式时序依赖。
  3. Configuration-on-Demand(COD):只配置参与构建的模块,跳过无关模块的 configuration 阶段,显著减少 Configuration 时间。
  4. 生效条件:Gradle 6.5+ 默认自带 Daemon;Parallel 与 COD 需显式开启;Kotlin DSL 与 Gradle 7.4+ 在 Android Studio 下默认启用 Parallel。
  5. 国内踩坑:
    • Windows Defender/360 会锁 jar,导致 Daemon 频繁被杀;
    • 多 SDK 镜像源时,COD 可能触发不同模块拉取不同版本缓存,出现“灵异”冲突;
    • CI 容器内存不足,开 Parallel 容易 OOM,需配合 org.gradle.workers.max 限制并发数。

答案

一、本地开发(macOS/Linux/Windows 通用)

  1. 全局生效:在 ~/.gradle/gradle.properties 中追加
    org.gradle.daemon=true // 其实 6.5+ 可省略,但显式写出可安抚面试官
    org.gradle.parallel=true
    org.gradle.configureondemand=true
    // 国内建议再补两条
    org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m
    org.gradle.caching=true // 构建缓存,与并行叠加收益更大

  2. 项目级兜底:把上述内容再抄一份到 project/gradle.properties,防止新同学没配全局。

  3. IDE 内校验:
    Android Studio Arctic Fox 及以上,Settings → Build → Compiler 里三项全部勾选;
    命令行 ./gradlew :app:assembleDebug --profile,打开 build/reports/profile 查看 Configuration 阶段是否缩短到 1 s 以内。

二、CI 场景(以国内最常用的 Jenkins + 自建 Kubernetes 为例)

  1. 镜像里预置 gradle.properties:
    FROM openjdk:11-jdk
    COPY ci-gradle.properties /root/.gradle/gradle.properties
    内容相比本地去掉 daemon,改为
    org.gradle.daemon=false // 容器一次性,Daemon 反而浪费内存
    org.gradle.parallel=true
    org.gradle.configureondemand=true
    org.gradle.workers.max=2 // 4 GB Pod 限并发,防止 OOM
    org.gradle.caching=true

  2. 流水线里再加增量缓存卷:
    volumes:

    • name: gradle-cache
      hostPath: /data/gradle-cache

三、验证脚本(可当场写给面试官)
./gradlew --status // 查看 Daemon 存活
./gradlew :lib:build --scan // 生成 Build Scan,Parallel 与 COD 收益一目了然

四、版本差异一句话总结
Gradle ≤5.6:COD 实验性功能,必须同时开 –-configure-on-demand 开关;
Gradle 7.0:Parallel 默认开启,但 COD 仍需显式;
Gradle 8.0:COD 转正,官方称“稳定”,可放心开。

拓展思考

  1. 为什么 CI 里把 Daemon 关掉反而更快?
    容器是一次性进程,Daemon 冷启动与 Warm-up 时间无法摊销,且占用内存导致 Kubernetes 节点频繁 OOMKill,整体排队时间 > Daemon 带来的编译收益。

  2. Parallel 开启后,测试任务顺序错乱导致 flaky test 怎么解?
    在 test 任务里显式加 mustRunAfter 或 finalizedBy,或者把 flaky 测试单独拆成 module,禁用并行:
    tasks.withType(Test).configureEach {
    if (name.contains("flaky")) maxParallelForks = 1
    }

  3. Configuration Cache(Gradle 7.5+ 稳定)与 COD 的区别?
    COD 只减少 configuration 入口,Configuration Cache 把整个 task graph 序列化到磁盘,二次构建直接加载,二者可叠加,但 Android 项目需插件全部适配,目前 AGP 8.1 已官方支持,可在 gradle.properties 加
    org.gradle.unsafe.configuration-cache=true
    提前体验,面试中抛出此点可展示技术前瞻性。