Build System
1. Gradle Lifecycle & Basics
Q: What are the 3 phases of a Gradle Build?
Every Gradle build has 3 phases:
1. Initialization
- Reads
settings.gradle.kts - Determines which modules to include
- Example:
include(":app", ":feature:home")
2. Configuration
- Executes ALL
build.gradle.ktsfiles - Creates task dependency graph (DAG)
- Critical: Runs on EVERY command, even
./gradlew --version
Common mistake:
// ❌ Runs every time (slow)
val data = URL("...").readText()
// ✅ Only runs when task executes
tasks.register("fetch") {
doLast { URL("...").readText() }
}
3. Execution
- Runs only requested tasks + dependencies
- Skips up-to-date tasks (incremental builds)
Q: What is the Task Graph (DAG)?
Dependency graph showing task execution order.
View it: ./gradlew assembleDebug --dry-run
Q: Groovy DSL vs Kotlin DSL?
| Feature | Groovy (.gradle) |
Kotlin (.gradle.kts) |
|---|---|---|
| Type Safety | Dynamic | Static (compile-time) |
| IDE Support | Basic | Excellent autocomplete |
| Refactoring | Manual | IDE support |
| Syntax | compileSdk 34 |
compileSdk = 34 |
Use Kotlin DSL for type safety and better IDE support.
Q: How to create custom tasks?
tasks.register("hello") {
doLast {
println("Hello!")
}
}
With dependencies:
tasks.register("taskB") {
dependsOn("taskA")
doLast { println("B runs after A") }
}
Skip tasks: ./gradlew build -x test
2. Android Gradle Plugin (AGP) Internals
Q: What happens when you build an APK?
When you run ./gradlew assembleDebug, here's what happens:
1. Manifest Merging
- Merges
AndroidManifest.xmlfrom app, libraries, and dependencies - Resolves conflicts using
tools:replaceortools:merge - Outputs single manifest in
build/intermediates/merged_manifests/
2. Resource Compilation (AAPT2)
- Compiles resources (
res/) into binary.flatfiles - Links them together and generates
R.javawith resource IDs - Creates
resources.arsc(resource table) - Why AAPT2? Incremental compilation - only recompiles changed resources
3. Source Compilation
kotlinc/javaccompiles your code to.classfiles (JVM bytecode)- Includes generated code:
R.java,BuildConfig.java, annotation processors (Room, Dagger) - Output:
.classfiles inbuild/intermediates/javac/
4. Desugaring (D8)
- Converts Java 8+ features for older Android versions
- Lambda → anonymous class, streams → loops, etc.
- Needed for API < 26
5. Dexing (D8 or R8)
- Converts
.class(JVM bytecode) to.dex(Dalvik bytecode) - Why? Android Runtime uses DEX, not JVM bytecode
- DEX is register-based (vs JVM stack-based) - faster on mobile
- 64K limit: 65,536 method limit per DEX file → use MultiDex if exceeded
- D8 for debug, R8 for release (R8 also does shrinking + obfuscation)
6. Packaging (zipflinger)
- Packages everything into APK (ZIP file):
AndroidManifest.xml(binary)classes.dex(maybeclasses2.dexif MultiDex)resources.arsc(resource table)res/(compiled resources)assets/(raw files)lib/(native libraries - .so files)
7. Signing (apksigner)
- Signs APK with keystore
- Creates
META-INF/with signatures - Debug build: auto-generated debug key
- Release: your production keystore
- Output: Signed APK ready to install
APK structure:
app-debug.apk
├── AndroidManifest.xml
├── classes.dex
├── resources.arsc
├── res/
├── assets/
├── lib/
└── META-INF/
Q: What's R.java? Generated class with integer IDs for resources.
App module:
public static final int activity_main = 0x7f0a001b; // final
Can use in switch statements.
Library module:
public static int button_text = 0x7f0e0005; // not final
Non-final because AGP renumbers IDs when merging libraries to avoid collisions.
Q: D8 vs R8?
| D8 | R8 | |
|---|---|---|
| Dexing | ✅ | ✅ |
| Shrinking | ❌ | ✅ |
| Obfuscation | ❌ | ✅ |
| When | Debug | Release |
R8 does everything D8 does PLUS removes unused code and renames classes.
Q: APK vs AAB (Android App Bundle)?
APK:
- Final installable file
- Contains everything for all devices (all densities, ABIs)
- Larger size
AAB:
- Upload to Play Store
- Play Store generates optimized APKs per device
- Users download only what they need (their density, ABI)
- 15% smaller on average
- Supports dynamic features (on-demand modules)
Q: How to debug build issues?
# See task execution order
./gradlew assembleDebug --dry-run
# Run specific task
./gradlew :app:compileDebugKotlin
# Get detailed report
./gradlew assembleDebug --scan
# Check where outputs are
build/
├── intermediates/ # Temp files
└── outputs/apk/ # Final APK
3. Build Variants
Q: What are build types and product flavors?
Build types define how the app is built:
debug: Debuggable, no optimization, debug signingrelease: Optimized, minified, production signing
Product flavors define different versions:
free/paiddemo/fullstaging/production
Variants = Build Type × Product Flavors (e.g., freeDebug, paidRelease)
Q: How to configure different environments?
Use buildConfigField for API URLs, feature flags:
productFlavors {
create("staging") {
applicationIdSuffix = ".staging"
buildConfigField("String", "API_URL", "\"https://staging.api.com\"")
}
create("production") {
buildConfigField("String", "API_URL", "\"https://api.com\"")
}
}
Access in code: BuildConfig.API_URL
Q: How to have different resources per flavor?
src/
├── main/ # Shared resources
├── staging/
│ └── res/
│ └── values/
│ └── strings.xml # Staging-specific strings
└── production/
└── res/
└── values/
└── strings.xml # Production-specific strings
Flavor-specific resources override main resources.
4. Dependencies
Q: implementation vs api?
implementation: (Prefer this)
- Dependency is internal
- Not exposed to consumers
- Faster builds (fewer recompilations)
api: (Use sparingly)
- Dependency is part of public API
- Consumers can access it
- Slower builds (more recompilations)
Example:
// Module A
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // Hidden from consumers
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Exposed
}
// Module B depends on A
// Can use coroutines ✅ (api)
// Cannot use OkHttp ❌ (implementation)
Q: Other dependency configurations?
| Configuration | Use Case |
|---|---|
compileOnly |
Annotation processors, provided by runtime |
runtimeOnly |
JDBC drivers, logging implementations |
testImplementation |
JUnit, MockK (unit tests only) |
androidTestImplementation |
Espresso (instrumented tests) |
Q: How to handle dependency conflicts?
Gradle picks highest version by default.
Check conflicts:
./gradlew :app:dependencies
Force version:
dependencies {
constraints {
implementation("com.squareup.okhttp3:okhttp:4.12.0") {
because("Security fix")
}
}
}
Exclude transitive dependency:
implementation("com.library:module:1.0") {
exclude(group = "com.google.guava", module = "guava")
}
5. Version Catalogs
Q: What are version catalogs?
Centralized dependency management in gradle/libs.versions.toml.
Benefits:
- Single source of truth for versions
- Type-safe accessors
- Easy to update versions across all modules
- IDE autocomplete support
Structure:
[versions]
kotlin = "1.9.20"
compose = "1.5.4"
[libraries]
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose" }
[bundles] # Group related dependencies
compose = ["compose-ui", "compose-material"]
[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Usage in build.gradle.kts:
dependencies {
implementation(libs.compose.ui) // Single lib
implementation(libs.bundles.compose) // Multiple libs
}
plugins {
alias(libs.plugins.kotlin.android)
}
6. R8 (Code Shrinking & Obfuscation)
Q: What does R8 do?
R8 is Google's tool that combines dexing with optimization. It does 4 things:
1. Code Shrinking (Tree-shaking)
- Removes unused classes and methods
- Starts from entry points (Activities, Services, etc.)
- Removes everything unreachable
2. Resource Shrinking
- Removes unused resources from
res/ - Only works if code shrinking is enabled
3. Obfuscation
- Renames classes/methods:
MyLongClassName→a - Harder to reverse engineer
- Smaller APK
4. Optimization
- Inlines functions
- Removes unused parameters
- Merges classes
Enable in build.gradle.kts:
buildTypes {
release {
isMinifyEnabled = true // Enable R8
isShrinkResources = true // Remove unused resources
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
Q: R8 vs ProGuard?
ProGuard: Old tool, separate steps: .class → ProGuard → .class → Dex → .dex
R8: Modern tool, one step: .class → R8 → .dex (faster, better)
Q: How to keep classes from obfuscation?
Use ProGuard rules in proguard-rules.pro:
# Keep model classes for GSON/Moshi
-keep class com.example.api.** { *; }
# Keep classes used with reflection
-keep class * extends com.example.BaseClass
# Keep annotation
-keep @interface com.example.Keep
-keep @com.example.Keep class * { *; }
Or use @Keep annotation:
@Keep
data class User(val name: String)
Q: What's the mapping file?
Obfuscation makes crashes unreadable:
NullPointerException at a.b.c(Unknown Source)
R8 generates mapping.txt that maps obfuscated names back to original:
com.example.MyClass -> a:
void onCreate() -> a
void onDestroy() -> b
Must upload to:
- Play Console (automatic crash reports)
- Firebase Crashlytics
- Other crash reporting tools
Location: app/build/outputs/mapping/release/mapping.txt
7. Signing
Q: What is APK/AAB signing?
Digital signature that:
- Proves app comes from you
- Ensures APK hasn't been modified
- Required for Play Store and device installation
- Same key required for updates
Q: Debug vs Release signing?
| Debug | Release |
|---|---|
| Auto-generated debug keystore | Your production keystore |
| Same on all machines | Must be protected |
| Can't publish to Play Store | Required for Play Store |
Q: What is Play App Signing?
Google manages your app signing key:
- You upload with upload key
- Google re-signs with app signing key
- If upload key compromised, can reset it
- Key never leaves Google's servers
Recommended for all new apps.
8. Build Performance
Q: How to make builds faster?
1. Configuration Cache (biggest win)
- Caches task graph from configuration phase
- Skip configuration if build scripts haven't changed
- Can save 1-3 seconds per build
Enable:
# gradle.properties
org.gradle.configuration-cache=true
2. Build Cache
- Reuses task outputs from previous builds
- Even works across branches and machines
Enable:
org.gradle.caching=true
3. Parallel Execution
- Builds modules in parallel
Enable:
org.gradle.parallel=true
org.gradle.workers.max=4 # Or number of CPU cores
4. Gradle Daemon
- Keeps JVM hot between builds (enabled by default)
5. Non-Transitive R Classes
- Changing resources doesn't trigger full recompilation
Enable:
android {
buildFeatures {
buildConfig = true
}
androidResources {
generateLocaleConfig = true
}
}
Q: How to find what's slowing down builds?
1. Build Scan:
./gradlew assembleDebug --scan
Uploads detailed report showing task timings.
2. Android Studio Build Analyzer:
- Build → Analyze Build
- Shows which tasks are slow
- Suggests optimizations
3. Profile build:
./gradlew assembleDebug --profile
Generates HTML report in build/reports/profile/.
Q: Common build performance issues?
| Issue | Solution |
|---|---|
| Many modules | Enable parallel execution |
| Slow compilation | Upgrade to latest AGP/Kotlin |
| Annotation processors | Switch to KSP (faster than KAPT) |
| Large resources | Use vector drawables, WebP |
| No caching | Enable build cache |
| Configuration slow | Avoid heavy work in build scripts |
9. Modularization
Q: Why modularize an Android project?
- Faster builds: Only changed modules rebuild
- Better encapsulation: Clear boundaries,
internalvisibility - Parallel development: Teams work independently
- Reusability: Share modules across apps
- Dynamic delivery: Feature modules on demand
Q: What module types exist?
| Type | Purpose |
|---|---|
| app | Main application module |
| library | Shared code, produces AAR |
| feature | Self-contained feature |
| core | Common utilities, base classes |
| data | Repository, data sources |
| domain | Business logic, use cases |
Q: What's dynamic feature delivery?
Feature modules delivered on-demand or by condition:
- Install-time: Included in base APK
- On-demand: Downloaded when needed
- Conditional: Based on device features
Uses App Bundle format, Play Store handles delivery.
10. CI/CD
Q: What's a typical Android CI pipeline?
1. Checkout code
↓
2. Lint (./gradlew lint)
↓
3. Unit tests (./gradlew test)
↓
4. Build APK/AAB (./gradlew assembleRelease)
↓
5. Instrumented tests (optional - slow)
↓
6. Upload to Play Store (if main branch)
GitHub Actions example:
name: Android CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: '17'
- name: Lint
run: ./gradlew lint
- name: Unit tests
run: ./gradlew test
- name: Build
run: ./gradlew assembleRelease
Q: How to automate Play Store publishing?
Use Gradle Play Publisher plugin:
plugins {
id("com.github.triplet.play") version "3.8.6"
}
play {
serviceAccountCredentials.set(file("play-store-key.json"))
track.set("internal") // or alpha, beta, production
}
Commands:
./gradlew publishBundle # Upload AAB
./gradlew promoteArtifact # Promote internal → alpha
Quick Reference
| Topic | Key Points |
|---|---|
| 3 Phases | Init (settings.gradle) → Config (build.gradle) → Execute (run tasks) |
| APK Build | Manifest → AAPT2 (resources) → Compile → Desugar → Dex → Package → Sign |
| Variants | Build Types (debug/release) × Flavors (free/paid) = Variants |
| Dependencies | implementation (internal), api (exposed), version catalogs |
| R8 | Code shrinking + obfuscation + optimization (release builds) |
| Signing | Debug (auto), Release (keystore), Play App Signing (Google manages) |
| Performance | Config cache, build cache, parallel execution, daemon |
| Modularization | Feature modules, faster builds, better encapsulation |
Interview Talking Points
When asked about Gradle:
- Explain 3 phases (especially configuration)
- Mention configuration cache for performance
- Know difference between
implementationandapi
When asked about APK build:
- Walk through 7 steps from source to signed APK
- Mention R8 for release builds
- Know what DEX is and 64K limit
When asked about optimization:
- Configuration cache (biggest win)
- Build cache and parallel execution
- Modularization for large projects
Red flags to avoid:
- Using
apieverywhere (useimplementation) - Heavy work in configuration phase
- Not using version catalogs
- Committing keystore passwords