Zygote
The build systemintermediate

Build tooling history

Eclipse and Ant, then Android Studio and Gradle, then Kotlin DSL and version catalogs. Every switch replaced the thing that had become the bottleneck, and each one kept the old output format alive.

12 minAPI 1–37 · 17interactivequizverified 2026-10-27

For most of Android's life, "building the app" meant one button in an IDE that did an unknowable number of things. The history of build tooling is the history of making that button explain itself.

TL;DR#

  • From 2008 to 2013 the toolchain was Eclipse + ADT + Ant: XML targets, no dependency graph, and no concept of a build variant.
  • Android Studio (announced 2013, 1.0 in December 2014) moved the IDE onto Gradle, and the Android Gradle Plugin taught Gradle what an APK is.
  • Gradle's real gift was not speed. It was a task graph, which made incremental builds, variants and dependency resolution possible at all.
  • The toolchain underneath changed separately: dx → D8 for dexing, and ProGuard → R8 for shrinking, optimisation and obfuscation.
  • Build files themselves changed from Groovy to Kotlin DSL, and version numbers moved into a single version catalog. Neither changes what an APK contains.

Mental model#

Think of a shipyard that has been rebuilt three times around the same ship design.

The slipway is the build system. The first one was a set of hand-pulled rails: it worked, but nothing told you what would happen until you pulled. The second was a rail network with switches — Gradle's task graph — where you could ask "what trains are scheduled?" before any of them left.

The yard tools are the compiler, the dexer and the shrinker. They got replaced more often than the slipway, because the ship's materials changed faster than its shape.

The shipping paperwork — the APK format, the manifest, the signing scheme — barely changed at all. That is the point: you can rebuild the yard completely and still launch the same ship.

Where the analogy breaks#

A shipyard is rebuilt once and then runs for decades. Android's build tooling has never stopped changing: a project created in 2013 and a project created in 2024 use the same Gradle, but almost nothing else in common. The migration is continuous, and most real codebases are a mixture of three eras at once — an old module still on Groovy, a new module on Kotlin DSL, and a buildSrc folder that belongs to neither.

The big picture#

Loading diagram…
Text description of this diagram

A timeline from 2009 to 2024 across three lanes. The IDE lane runs Eclipse ADT (2009), Android Studio announced in 2013 and reaching 1.0 in December 2014, Android Studio 3.0 in 2017 with Kotlin support. The build system lane runs Ant with the ADT plugin in 2009, Gradle and the Android Gradle Plugin from 2013, Instant Run in 2015, Kotlin DSL around 2019, version catalogs in 2021, and configuration cache maturing in 2021. The toolchain lane runs the dx dexer in 2009, D8 in 2017, R8 replacing ProGuard in 2018, KSP in 2021 and the K2 compiler in 2024. Against a backdrop of three eras: the Eclipse ADT era, the Gradle era, and the Kotlin DSL and modern AGP era.

The timeline's most striking feature is that the compiler lane changes at a different rhythm from the IDE lane. The IDE has had two real phases — Eclipse and Studio. The dexer has had three. The build system has had essentially one since 2013, even though how you write its files has changed twice.

Deep dive#

The Ant era: a build script is a list of steps#

An Ant build is a sequence of named targets with dependencies between them. It is a scripting language for file operations: copy these resources, invoke this tool, zip the result. There is no model of "an Android app" anywhere in it.

That has two consequences. First, nothing can be incremental for you — if you want to skip a step, you write the timestamp check yourself. Second, nothing can be reused — every project wrote its own build script, and every one was slightly different. Adding a library meant downloading a JAR and putting it in a libs/ folder.1

The ADT plugin made this bearable by generating the script for you. That is also why the build was opaque: the thing you edited and the thing that ran were both generated, and neither was documented in the way a programming language is.

2013–2014: Gradle arrives with a model#

Gradle brought three things Ant could not express.

A task graph. Tasks declare inputs and outputs; Gradle decides what must run. That single change made incremental builds possible as a general property rather than a per-project hack.

Dependency resolution with conflict resolution. implementation("androidx.core:core-ktx:1.13.1") is a graph edge, not a file copy. If two modules ask for different versions, Gradle picks one and tells you it did.

Build types and product flavours. This is the concept Android calls a : the cross product of how you build (debug, release) and what you build (free, paid). A project with two build types and two flavours has four variants, four merged manifests, and four APKs. Ant had no way to express this at all.2

The Android Gradle Plugin is what makes this Android-specific. Gradle alone knows about Java tasks; AGP adds assembleRelease, bundleRelease, resource processing, manifest merging, dexing and signing. The plugin and the build tool are separate projects with a compatibility matrix between them — a fact that every upgrade eventually teaches you.

The toolchain beneath: dx to D8, ProGuard to R8#

The build system describes what to run; the tools do the work. Those changed on their own schedule.

dx → D8. dx converted JVM class files to DEX. D8 replaced it and, crucially, took over desugaring: rewriting Java 8 language features — lambdas, default methods, try-with-resources — into forms older Android versions can execute. That is what made it possible to use modern Java syntax while still supporting old devices.3

ProGuard → R8. ProGuard shrank and obfuscated, then a separate dexer ran afterwards. R8 does shrinking, optimisation, obfuscation and dexing in a single pass. Fewer passes over the bytecode means faster builds and, often, smaller output.4

Both replacements shared a design constraint: the old configuration format had to keep working. R8 still reads ProGuard rules, because the entire ecosystem had ten years of them. That is why your proguard-rules.pro file is still called that.

Groovy to Kotlin DSL#

Gradle build files were Groovy. Groovy is dynamic, terse and forgiving — which is exactly why build files written in it are hard to refactor and hard for an IDE to complete.

The Kotlin DSL (build.gradle.kts) makes the build script a statically typed Kotlin program. The practical difference is not elegance; it is that your IDE can tell you that minSdk is an Int before you run anything.

// build.gradle.kts
android {
    namespace = "com.example.app"
    compileSdk = 36
 
    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 24
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"
    }
 
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro",
            )
        }
    }
}

Nothing above is a new capability. Every line has a Groovy equivalent. What changed is that a typo in isMinifyEnabled is now a compile error in the build script rather than a silently ignored property.

Version catalogs#

The last era's problem was not capability but coordination. A large project had the same version string in eight build files, and the AndroidX BOM was a partial answer to only one dependency family.

A puts every version in one TOML file:

# gradle/libs.versions.toml
[versions]
kotlin = "2.0.21"
coroutines = "1.9.0"
 
[libraries]
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
dependencies {
    implementation(libs.kotlinx.coroutines.android)
    testImplementation(libs.kotlinx.coroutines.test)
}

The catalog is generated into a type-safe accessor, so libs.kotlinx.coroutines.android completes in the IDE and fails at configuration time if the alias does not exist. It also makes version drift visible: one number, one place.

What "build speed" actually means#

The phrase hides three different problems, and each era solved a different one.

Incremental correctness is whether a rebuild after a one-line change produces the same artifact as a clean build. Ant had no answer; Gradle's input and output declarations are the answer, and an undeclared input is the bug.

Configuration cost is the fixed price of every invocation, independent of what changed. It grew as projects became modular and eventually became the dominant cost for large builds. The configuration cache is the answer.

Tool throughput is how fast the compilers and dexers run on the work they are given. Kotlin's K2 front end and D8's replacement of dx are answers here.

When someone says "Gradle is slow", the useful follow-up is: slow to start, slow to notice a change, or slow to do the work? The three have different fixes, and only one of them is a faster laptop.

How it evolved#

2009–2013Eclipse ADT + AntXML targets, hand-managed JARs, no variants
2013–2019Gradle + AGPTask graph, dependency resolution, variants
2019–nowKotlin DSL + catalogsTyped build scripts, one version source

The constant across all three eras: the output format never changed. An APK built in 2013 and one built today are both ZIP files with a manifest, resources and DEX inside. The signing scheme evolved — v2 in Android 7, v3 after that — but the container did not. That stability is why the tooling could be replaced without breaking the Play Store.

Architecture changes29 releases
APIVersionWhat changed
11.0
  • Initial AOSP platform: Linux kernel, Dalvik VM, core libraries
  • The first zygote-based app spawning model
41.6 Donut
  • Support for different screen densities via resource qualifiers
52.0 Eclair
  • First multi-core-friendly scheduling and graphics work
82.2 Froyo
  • Trace-based JIT compiler added to Dalvik
  • Support for x86 and ARMv7 optimisations
92.3 Gingerbread
  • Concurrent garbage collector added to Dalvik (GC pause reduction)
113.0 Honeycomb
  • Hardware acceleration for the View system (opt-in per app)
133.2 Honeycomb
  • New screen-size resource qualifiers for compatibility scaling
144.0 Ice Cream Sandwich
  • One system image for phones and tablets
  • Hardware acceleration on by default
164.1 Jelly Bean
  • Project Butter: vsync, triple buffering, Choreographer
  • RenderThread architecture begins
174.2 Jelly Bean
  • Multi-user support on tablets
  • SELinux enforcement begins in the kernel (behind the scenes)
184.3 Jelly Bean
  • SELinux enforcement rolled out more broadly
  • Binder changes for restricted profiles
194.4 KitKat
  • Android Runtime (ART) appears as an experimental runtime
  • Kernel moved under a tighter vendor control
204.4W KitKat (Wear)
  • First time the platform shipped for a new form factor
215.0 Lollipop
  • ART becomes the only runtime: ahead-of-time compilation at install
  • 64-bit ARM and x86 support
  • RenderThread and OpenGL ES 3.1
  • Project Volta and JobScheduler
  • SELinux fully enforcing
225.1 Lollipop
  • Device protection ties verified boot to Google accounts
236.0 Marshmallow
  • Runtime permission model changes how every app asks for access
  • Doze batches background work
  • Fingerprint HAL
  • Adoptable storage
247.0 Nougat
  • Project Treble groundwork
  • A/B seamless updates
  • Hybrid JIT + AOT compilation in ART
  • MediaServer split into sandboxed processes
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • Background execution limits
  • Broadcast and service restrictions
  • HIDL for HALs
278.1 Oreo
  • Android Go: a lighter configuration of the same platform
289 Pie
  • App Standby Buckets
  • Mainline groundwork
  • Non-SDK interface restrictions (greylists) begin
  • BiometricPrompt
2910
  • Project Mainline: APEX and updatable system modules
  • Scoped storage
  • Bubbles
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules
  • App hibernation
  • Package visibility filtering
  • Incremental app installs
3112
  • Bluetooth permissions split into the nearby-devices family
  • Exact alarm permissions
  • Foreground service launch restrictions
3212L
  • Window size classes and activity embedding
3313 Tiramisu
  • Granular media permissions replace READ_EXTERNAL_STORAGE
  • The notification permission becomes a runtime permission
  • Predictive back gesture
3414 Upside Down Cake
  • Foreground service types are mandatory
  • Selected photos access via READ_MEDIA_VISUAL_USER_SELECTED
3515 Vanilla Ice Cream
  • 16 KB page size support for native libraries
  • Edge-to-edge is enforced when targeting 35
3616 Baklava
  • The release moves to Q2 (June) with a Q4 feature drop
  • Two SDK releases per year starting with 36
  • Desktop windowing arrives in developer options then stable
3717 Cinnamon Bun
  • Strict per-device app memory limits are enforced by the system
  • Generational garbage collection is added to ART's Concurrent Mark-Compact collector
  • A lock-free MessageQueue for apps targeting API 37
  • Compose-first: new platform and Jetpack APIs target Compose only; View libraries enter maintenance mode
  • Hybrid APK signing (v3.2) combines classical and ML-DSA post-quantum signatures
  • Native library dynamic code loading must be read-only

Under the hood#

The build history is legible in four files that every Android project still has:

Those are paths in your own project, not in AOSP, and that is the point: the application build system is a different world from the platform build system, which is its own story. The one file that has survived every era is the manifest — AndroidManifest.xml has been read by every build system Android has ever had, because it is what the platform installs against, not what the build tool invents.

Curiosity corner#

  • The Gradle wrapper is the only build tool most teams version-control. gradlew and gradle-wrapper.properties are committed so that every machine uses the same Gradle, without anyone installing Gradle.
  • "Instant Run" was the most ambitious failure in Android tooling. It tried to hot-swap code into a running app; the constraints of ART made it unreliable, and it was replaced by Apply Changes, which is honest about what it can and cannot do.
  • proguard-rules.pro survives R8 because the rule format, not the tool, was the contract.

Try it#

Ask Gradle what it would do

./gradlew :app:tasks --all

Example output
./gradlew :app:tasks --all

> Task :app:tasks

------------------------------------------------------------
All tasks runnable from project ':app'

Android tasks
-------------
assembleDebug - Assembles main output for variant debug
assembleRelease - Assembles main output for variant release
bundleRelease - Assembles bundle for variant release
...
Dry run a release build

./gradlew :app:assembleRelease --dry-run

Example output
:app:preBuild SKIPPED
:app:preReleaseBuild SKIPPED
:app:mergeReleaseResources SKIPPED
:app:processReleaseResources SKIPPED
:app:compileReleaseKotlin SKIPPED
:app:minifyReleaseWithR8 SKIPPED
:app:packageRelease SKIPPED
:app:assembleRelease SKIPPED

The SKIPPED lines are not failures. --dry-run prints the graph and executes nothing, which is why it finishes in seconds instead of minutes.

Build it#

Run ./gradlew :app:assembleRelease --dry-run on any Android project you have and count the tasks before assembleRelease. Then open the build pipeline and match each task to a stage.

Check yourself#

Check yourself

1 / 12

Put these build stages in order, from first to last.

Drag, or use the arrows to reorder.

  1. 1AAPT2 link produces resources.arsc and the R class
  2. 2D8 converts class files to DEX
  3. 3kotlinc compiles Kotlin to JVM class files
  4. 4apksigner writes the v2/v3 signature blocks
0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. wikiAndroid version historyWikipediaaccessed 2026-10-27↩
  2. docsConfigure your builddeveloper.android.comaccessed 2026-10-27↩
  3. docsJava 8+ API desugaring supportdeveloper.android.comaccessed 2026-10-27↩
  4. docsShrink, obfuscate, and optimize your appdeveloper.android.comaccessed 2026-10-27↩
  5. docsOptimize your build speeddeveloper.android.comaccessed 2026-10-27↩
  6. docsGradle user manualdocs.gradle.orgaccessed 2026-10-27↩
  7. docsapksignerdeveloper.android.comaccessed 2026-10-27↩
  8. docsBuild Androidsource.android.comaccessed 2026-10-27↩