跪拜 Guibai
← Back to the summary

Gradle 9.7 Parallel Configuration Nearly Doubles Android Sync Speed

Android recently collaborated with the Gradle team to introduce the new Gradle Isolated Projects in version 9.7.0. Simply put, Gradle is now changing the 'configuration phase' of large multi-module projects into an architecture that can safely run in parallel, while also enabling per-module incremental caching in the future.

For Android development, Gradle Projects like app, feature-home, core-network, and core-ui previously appeared to be independent modules, but during the configuration phase, they could actually read and write each other's state. This made it difficult for Gradle to safely configure them concurrently. Now, Isolated Projects imposes a strong constraint boundary between these Projects, allowing parallel configuration. Starting with Gradle 9.7, this support has been promoted from experimental to incubating, and it will become the default mode in the future.

A future Gradle build will generally look like this:

Initialization
     ↓
Configuration
     ↓
Execution

Read settings       Execute each module's build.gradle       javac/kotlinc/package...
                    Create Tasks
                    Configure Plugins
                    Resolve various Project states

A large portion of the time spent on what everyone calls Android Studio's Sync Project with Gradle Files is consumed by the Configuration phase. For example, in a large project with 500 modules, the logic was previously close to:

:app
 │
 ├─ configure
 ↓
:core
 │
 ├─ configure
 ↓
:network
 │
 ├─ configure
 ↓
:feature-home
 │
 ├─ configure
 ↓
...

However, in the past, this configuration behavior could not simply be thrown into a thread pool because Gradle's historical Build DSL allowed patterns like this:

// :app/build.gradle.kts


val version = rootProject.version


project(":network").tasks.named("xxx") {
    ...
}


rootProject.subprojects {
    // Modify other projects
}

This means that while A is being configured, it can touch B, and B might touch C:

Project A configuration
        │
        ├──────────► Project B mutable state
        │
        └──────────► Root Project mutable state

If you try to run them synchronously in a multi-threaded environment, Gradle cannot guarantee execution order and state consistency. Callback order and plugin behavior would become chaotic, and task registration could also have problems. Therefore, Gradle multi-module projects have long suffered from a serious 'shared mutable state during configuration' problem. The core thing Isolated Projects does is actually quite simple; it mainly stipulates:

        :app
     ┌─────────┐
     │ mutable │
     │ state   │
     └─────────┘
          X
          │ Forbidden to touch directly
          X
     ┌─────────┐
     │ mutable │
     │ state   │
     └─────────┘
     :network

Each Project's configuration logic can only safely operate on its own mutable state. For example:

rootProject.version
project(":foo").tasks
project(":foo").dependencies
project(":foo").configurations
project(":foo").extensions

This kind of cross-Project 'reading/modifying mutable state' will be judged by Gradle as an Isolated Projects violation. The official recommendation can be simply understood as:

Except for a few immutable getters, basically do not call anything on another Project.

Immutable information determined before configuration starts, such as another Project's name, path, projectDir, and buildFile, can still be read. So in reality, this establishes a new concurrency contract on the Gradle DSL. With this contract, you can achieve:

Before


Project A ──► B
   │        ▲
   ▼        │
Project C ◄──┘


Don't know who depends on whose state
→ Cannot parallelize




Isolated Projects


┌ A ┐   ┌ B ┐   ┌ C ┐   ┌ D ┐
└───┘   └───┘   └───┘   └───┘
  │       │       │       │
 CPU1    CPU2    CPU3    CPU4


→ Can safely configure in parallel

So the major practical benefit of Gradle 9.7 is Parallel Project Configuration.

During an Android Studio Sync, the Studio asks Gradle to build a large number of Tooling Models, for example:

Gradle


:app
 ├─ Android model
 ├─ dependency model
 ├─ source sets
 └─ variants


:featureA
 ├─ Android model
 ├─ dependency model
 └─ variants


:featureB
 ...
        │
        ▼
Android Studio

Previously, because Project configuration itself had shared state, the parallelism for the IDE to fetch these models was always limited. But now, with Isolated Projects enabled:

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

The results from Gradle's own official test project are quite significant:

For a 5000+ Project Android monorepo, it can even achieve an optimization from 5m09s -> 2m44s. So while the perception for a typical app with a dozen modules might be average, the impact on an Android monorepo with hundreds to thousands of modules is quite significant.

Furthermore, it is actually on the same track as Configuration Cache, which can be roughly understood as:

Configuration Cache
       │
       │ Solves:
       ▼
Last configuration unchanged?
→ Then skip the entire Configuration run this time




Isolated Projects
       │
       │ Solves:
       ▼
Configuration must re-run?
→ Then run each Project in parallel

So the overall architecture situation is roughly like:

             Configuration Cache
                       │
              ┌────────┴────────┐
              │                 │
          Cache Hit          Cache Miss
              │                 │
       Configuration          Isolated
        Fully Skipped         Projects
                                │
                     Project A ─┬─ Project B
                                ├─ Project C
                                └─ Project D
                             Parallel Configuration

That is, Isolated Projects is actually built directly on top of the Configuration Cache infrastructure. Therefore, enabling org.gradle.isolated-projects=true will cause Gradle to automatically enable Configuration Cache. Explicitly turning off Configuration Cache will even result in an error.

However, it is completely different from org.gradle.parallel=true. parallel mainly solves:

Execution Phase


:app:compile
:libA:compile
:libB:compile
     ↓
Task Parallelism

While Isolated Projects solves:

Configuration Phase


configure :app
configure :libA
configure :libB
     ↓
Project Configuration Parallelism

These two are not the same thing. However, Gradle officially stated that this support was implemented in collaboration with Google and JetBrains, meaning the project, the IDE, all participated in this optimization.

But older projects might face some risks. For example, if custom Gradle scripts or third-party plugins have cross-project access, enabling Gradle Isolated might directly cause configuration failures.

But honestly, for the China region, the biggest time sink for Sync is not here at all. In reality, this optimization is mostly negligible, and upgrading to Gradle 9.7.0 still requires a lot of courage.

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

背对疾风

These Android folks have nothing else to do all day but tinker with Gradle.