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.kts files
  • 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.xml from app, libraries, and dependencies
  • Resolves conflicts using tools:replace or tools:merge
  • Outputs single manifest in build/intermediates/merged_manifests/

2. Resource Compilation (AAPT2)

  • Compiles resources (res/) into binary .flat files
  • Links them together and generates R.java with resource IDs
  • Creates resources.arsc (resource table)
  • Why AAPT2? Incremental compilation - only recompiles changed resources

3. Source Compilation

  • kotlinc/javac compiles your code to .class files (JVM bytecode)
  • Includes generated code: R.java, BuildConfig.java, annotation processors (Room, Dagger)
  • Output: .class files in build/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 (maybe classes2.dex if 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 signing
  • release: Optimized, minified, production signing

Product flavors define different versions:

  • free / paid
  • demo / full
  • staging / 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: MyLongClassNamea
  • 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, internal visibility
  • 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 implementation and api

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 api everywhere (use implementation)
  • Heavy work in configuration phase
  • Not using version catalogs
  • Committing keystore passwords

results matching ""

    No results matching ""