androidengineers.Book a session

CI/CD Pipeline

CI Basics & Caching Gradle

article25 minMedium

A fast, reliable CI pipeline is the foundation of a productive Android team. The biggest wins come from smart Gradle caching and parallelism — not from faster machines.

Gradle Build Performance Fundamentals

Understand the Build Phases

initialization → Configuration → Task graph → Execution
  • Configuration: build.gradle.kts evaluated for all projects — avoid I/O or network calls here
  • Execution: only tasks that are not up-to-date run — cache maximizes this benefit

Enable Build Caching

// gradle.properties
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=false                // false in CI; daemon is for local dev
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g

// Enable configuration cache (Gradle 8+)
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn

Remote Build Cache (GitHub Actions)

# .github/workflows/ci.yml
- name: Gradle cache
  uses: gradle/gradle-build-action@v2
  with:
    cache-read-only: ${{ github.ref != 'refs/heads/main' }}
    # Write cache only on main; PRs use read-only (saves cache quota)

- name: Build & test
  run: ./gradlew assembleDebug testDebugUnitTest --build-cache

Gradle Home Cache (Dependencies)

Cache the Gradle wrapper and dependencies separately from the build output cache:

- name: Setup Java
  uses: actions/setup-java@v3
  with:
    java-version: '17'
    distribution: 'temurin'
    cache: 'gradle'  # caches ~/.gradle/caches and ~/.gradle/wrapper

Android-Specific Caching Targets

- name: Cache Gradle outputs
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
      ~/.android/build-cache
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts', '**/gradle-wrapper.properties') }}
    restore-keys: |
      ${{ runner.os }}-gradle-

Parallelism: Module-Level

Split your CI into parallel jobs by test type:

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - run: ./gradlew testDebugUnitTest

  lint:
    runs-on: ubuntu-latest
    steps:
      - run: ./gradlew lintDebug

  instrumented-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 33
          script: ./gradlew connectedDebugAndroidTest

Build Scan: Diagnosing Slow Builds

// settings.gradle.kts
plugins {
    id("com.gradle.enterprise") version "3.16.2"
}

gradleEnterprise {
    buildScan {
        termsOfServiceUrl = "https://gradle.com/terms-of-service"
        termsOfServiceAgree = "yes"
        publishAlways()
    }
}

Build scans show task execution timeline, cache hit/miss rates, and which tasks are the bottleneck.

Common Speed Gains

OptimizationTypical Savings
Gradle build cache (local)30–60% on incremental builds
Remote cache (CI)40–70% on PR builds
Parallel modulesLinear with module count
Configuration cache10–30% on non-incremental
kapt → KSP migration20–40% on annotation processing
Avoid api() over implementation()Fewer downstream recompilations

Key Takeaways

RuleWhy
org.gradle.caching=trueReuse outputs from previous builds
cache: 'gradle' in setup-javaCaches the dependency download step
Write cache only on main branchPR builds get read-only; avoids cache poisoning
Parallelize by job typeUnit tests, lint, and instrumented tests run concurrently
Use Build Scans to diagnoseYou can't optimize what you can't measure

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
CI Basics & Caching Gradle | Android System Design | Android Engineers