Skip to main content

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​

ToolMain responsibility
GradleBuild lifecycle, task graph, dependency resolution, caching
AGPAndroid variants, manifests/resources, DEX/package/sign/test/lint integration
Kotlin/Java compilersSource to JVM class output
AAPT2Compile/link Android resources and manifest-related packaging data
D8Class bytecode/desugaring to DEX
R8Whole-app shrinking, optimization, obfuscation, DEX
apksigner/bundle toolingArtifact 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.
Interview-ready answer

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.

Continue​

  1. Gradle lifecycle, tasks, Providers, and caching
  2. Build types, flavors, variants, and source sets
  3. Dependencies, api/implementation, and version catalogs
  4. R8, keep rules, resource shrinking, and retrace
  5. Signing, secrets, APK/AAB, and release identity
  6. Build performance, CI, and interview scenarios