Android build system — from source to signed artifact
The build is a transformation pipeline
An Android project contains source code, resources, manifests, generated code, native libraries, dependency metadata, and variant configuration. Gradle orchestrates tasks; Android Gradle Plugin (AGP) contributes Android-specific models and tasks.
source + resources + manifests + dependencies + configuration
│
Gradle task graph + AGP
│
merge/generate/compile/process/desugar/shrink
│
DEX + packaged resources
│
sign + verify artifact
│
APK or AAB
What each tool owns
| Tool | Main responsibility |
|---|---|
| Gradle | Build lifecycle, task graph, dependency resolution, caching |
| AGP | Android variants, manifests/resources, DEX/package/sign/test/lint integration |
| Kotlin/Java compilers | Source to JVM class output |
| AAPT2 | Compile/link Android resources and manifest-related packaging data |
| D8 | Class bytecode/desugaring to DEX |
| R8 | Whole-app shrinking, optimization, obfuscation, DEX |
| apksigner/bundle tooling | Artifact signing/verification/delivery packaging |
The exact tasks/tool boundaries evolve with AGP. Treat task names as observable implementation, not a permanent public architecture.
APK versus Android App Bundle
- APK: installable package for a device.
- AAB: publishing format containing code/resources/metadata from which a distributor such as Google Play generates device-specific APKs.
An AAB is not installed directly on a normal device. Test generated APK delivery—languages, densities, dynamic features, signing—not only the universal local debug APK.
Debug success does not prove release success
Release can differ in:
- R8/resource shrinking and obfuscation;
- signing certificate and API allowlists;
BuildConfig/manifest placeholders/resources;- server base URL and network-security config;
- debug-only dependencies/tools;
- debuggability and runtime profiles;
- Play-generated split delivery.
✅ Release principle
Every critical flow needs tests against a minified, signed, release-like variant before rollout.
Reproducible and hermetic direction
A build should produce the same logical output from declared inputs. Undeclared environment, current time, network downloads during custom tasks, absolute machine paths, and mutable dependency versions reduce reproducibility/cacheability.
❌ Hidden task input
tasks.register("writeServer") {
doLast {
file("server.txt").writeText(System.getenv("SERVER_URL"))
}
}
Gradle cannot reliably cache a task if inputs/outputs are undeclared and it writes arbitrary files.
✅ Declared task model
Use typed task properties annotated as inputs/outputs and Providers for lazy configuration. Validate missing configuration explicitly and keep secrets out of artifact/logs.
Build configuration is production code
A one-line Gradle change can alter signing, exported components, code removal, server environment, permissions, or delivery. Review build logic with the same rigor as Kotlin:
- ownership and tests;
- versioned conventions;
- minimal privileges/secrets;
- migration plan;
- measurable build impact;
- release rollback/forward fix.
Gradle models the build lifecycle/task DAG and resolves dependencies; AGP adds Android variants and tasks for resources, manifests, compilation, DEX, shrinking, packaging, signing, testing, and publishing. APK is installable; AAB is a publishing input used to generate device APKs. I treat release as a distinct tested product because shrinking, signing, configuration, and split delivery can differ from debug.