Android 最近和 Gradle 官方合作,在 9.7.0 版本推出了全新的 Gradle Isolated Projects ,简单来说就是 Gradle 现在开始把大型多模块工程的「配置阶段」改成能安全并行运行,同时未来还能按模块增量缓存的架构。

现在对 Android 开发来说,app、feature-home、core-network、core-ui 这些 Gradle Project,以前虽然看起来都是独立 module,但配置阶段其实可以互相读写状态,所以 Gradle 很难安全地同步配置它们,然后现在 Isolated Projects 给这些 Project 之间加了一道强约束边界,然后就可以并行配制了,在 Gradle 9.7 开始,这个支持从 experimental 提升到了 incubating,未来还会变成为默认模式。
以后 Gradle 一次构建大体是:
arduino
Initialization
↓
Configuration
↓
Execution
读 settings 执行每个 module 的 build.gradle javac/kotlinc/package...
创建 Task
配置 Plugin
解析各种 Project 状态
也就是大家平时说的 Android Studio Sync Project with Gradle Files 很大一部分时间都花在 Configuration 上,比如一个 500 个 module 的大型项目,过去逻辑上接近:
ruby
:app
│
├─ configure
↓
:core
│
├─ configure
↓
:network
│
├─ configure
↓
:feature-home
│
├─ configure
↓
...
但是在过去这个配置行为不能简单地丢进线程池,因为 Gradle 历史上的 Build DSL 存在这种玩法:
javascript
// :app/build.gradle.kts
val version = rootProject.version
project(":network").tasks.named("xxx") {
...
}
rootProject.subprojects {
// 修改其他 project
}
也就是说 A 配置到一半可以摸 B,B 又可能摸 C :
arduino
Project A configuration
│
├──────────► Project B mutable state
│
└──────────► Root Project mutable state
如果把他们放到多线程里同步支持,但是出现这种情况的话:
css
Thread 1 → configure A
Thread 2 → configure B
Thread 3 → configure C
搜一 Gradle 保证不了执行顺序和状态一致性,callback 顺序和插件行为也会混乱,task 的注册也可能出现问题,所以 Gradle 多模块工程长期存在一个很严重的「配置阶段共享可变状态」问题 ,而 Isolated Projects 做的核心事情实际上也很简单,它主要规定了:
arduino
:app
┌─────────┐
│ mutable │
│ state │
└─────────┘
X
│ 禁止直接碰
X
┌─────────┐
│ mutable │
│ state │
└─────────┘
:network
每个 Project 的配置逻辑只能安全操作自己的 mutable state,例如:
java
rootProject.version
project(":foo").tasks
project(":foo").dependencies
project(":foo").configurations
project(":foo").extensions
这类跨 Project 「读取/修改可变状态」会被 Gradle 判成 Isolated Projects violation,官方甚至建议可以简单理解成:
除少数 immutable getter 外,基本别去调用另一个
Project的东西。
而诸如另一个 Project 的 name 、path 、projectDir 、buildFile 这种在配置开始前已经确定的 immutable 信息还是可以读取,所以实际上这是在 Gradle DSL 上建立一个新的 concurrency contract,有了这个 contract 就可以:
css
以前
Project A ──► B
│ ▲
▼ │
Project C ◄──┘
不知道谁依赖谁的状态
→ 不敢并行
Isolated Projects
┌ A ┐ ┌ B ┐ ┌ C ┐ ┌ D ┐
└───┘ └───┘ └───┘ └───┘
│ │ │ │
CPU1 CPU2 CPU3 CPU4
→ 可以安全并行配置
所以 Gradle 9.7 大的实际收益就是 Parallel Project Configuration。
而在 Android Studio Sync 的时候,Studio 会让 Gradle 构建大量 Tooling Model,例如:
ruby
Gradle
:app
├─ Android model
├─ dependency model
├─ source sets
└─ variants
:featureA
├─ Android model
├─ dependency model
└─ variants
:featureB
...
│
▼
Android Studio
以前 Project configuration 本身存在共享状态,IDE 获取这些 model 的并行度一直受到限制,但是现在 Isolated Projects 开启以后:
ruby
CPU 1 → configure :app → model
CPU 2 → configure :featureA → model
CPU 3 → configure :featureB → model
CPU 4 → configure :core → model
CPU 5 → configure :network → model
Gradle 官方自己的测试工程效果就很显著:

如果是 5000+ Project Android monorepo ,甚至可以做到 5m09s -> 2m44s 这种优化,所以这东西对十几个 module 的普通 App 感知可能一般,但是对几百到几千 module 的 Android monorep 影响还是挺大的。
而且它和 Configuration Cache 其实是一条路线,可以粗略理解成:
Configuration Cache
│
│ 解决:
▼
上次配置没变?
→ 那这次整个 Configuration 都别跑了
Isolated Projects
│
│ 解决:
▼
配置必须重新跑?
→ 那各个 Project 并行跑
所以整体架构情况大概类似:
css
Configuration Cache
│
┌────────┴────────┐
│ │
Cache Hit Cache Miss
│ │
Configuration Isolated
全跳过 Projects
│
Project A ─┬─ Project B
├─ Project C
└─ Project D
并行配置
也就是 Isolated Projects 其实是直接建立在 Configuration Cache 基础设施之上 ,所以开启 org.gradle.isolated-projects=true ,Gradle 就会自动启用 Configuration Cache,显式关掉 Configuration Cache 甚至会报错。
不过它和 org.gradle.parallel=true 完全不是一回事,parallel 主要解决的是:
ruby
Execution Phase
:app:compile
:libA:compile
:libB:compile
↓
任务并行
而 Isolated Projects 解决的是:
ruby
Configuration Phase
configure :app
configure :libA
configure :libB
↓
Project 配置并行
这两者还不太一样,不过 Gradle 官方也说了,这是他们和 Google、JetBrains 合作一起实现的支持,也就是从项目到 IDE 到参与了这次优化。
但是老项目可能会有一些风险,比如自定义 Gradle 脚本或者第三方 plugin 如果存在 cross-project access , 打开 Gradle Isolated 可能就直接配置失败了。
不过说真的,对于国区来说,Sync 最大耗时根本不在这里,实际上这个优化大部分时候聊胜于无,而且升级 Gradle 9.7.0,还是很需要勇气的。