Share
Embed
Dompting the Elephant: Making Sense of Gradle for Android Developers
In this talk, we’ll demystify how Gradle works under the hood. You’ll gain a clear mental model of the build lifecycle, understand the difference between configuration and execution phases, and learn how tasks, plugins, and dependencies interact.
Yves Kalume
November 06, 2025
Transcript
71 slides
-
Gradle Build What you are trying to build build.gradle(.kts) Project
Tasks Properties : Build script -
Gradle Build Settings Project build.gradle(.kts) settings.gradle(.kts) include(“:project”) Droid rootProject.name =
“Droid” gradle.properties ./gradlew <task> -P <option> -
Project public interface Project extends Comparable<Project> { String DEFAULT_BUILD_FILE =
"build.gradle"; String DEFAULT_BUILD_DIR_NAME = "build"; String GRADLE_PROPERTIES = "gradle.properties"; File getProjectDir(); File getBuildDir(); void apply(Map<String, ?> options); void afterEvaluate(Action<? super Project> action); void dependencies(Action<? super DependencyHandler> action); void repositories(Action<? super RepositoryHandler> action); Task task(String name); ... } -
Initialization (Build phases) • Create a settings instance and evaluate
settings.gradle(.kts) • Determine which project are included and create their instances -
Con fi guration (Build phases) • Reads each build.gradle •
Con fi gures a Project instance for every module -
Con fi guration (Build phases) processTestResources Build assemble jar check
classes compileJava processResources test testClasses compileTestJava DAG (Directed Acyclic Graph) dependsOn dependsOn -
Plugin android { compileSdk = 34 defaultConfig { applicationId =
"ke.droidcon.app" } } -
Plugin android { compileSdk = 34 defaultConfig { applicationId =
"ke.droidcon.app" } } plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } -
Plugin abstract class SamplePlugin : Plugin<Project> { override fun apply(project:
Project) { project.tasks.register("MyCustomTask") { doLast { println("Hello world from the build file!") } } } } -
Binary plugin • Implemented in a compiled language (.kt fi
le) • Is packaged as a JAR fi le -
Binary plugin MyProject ├── my-plugin │ ├── themodule │ │
├── src │ │ └── build.gradle.kts │ └── settings.gradle.kts ├── app │ └── src └── settings.gradle.kts -
Binary plugin plugins { `kotlin-dsl` } gradlePlugin { plugins {
create("droidconke") { id = "ke.droidcon.greeting" implementationClass = "ke.droidcon.GreetingPlugin" } } } MyProject/my-plugin/build.gradle.kts -
abstract class GreetingTask : DefaultTask() { @Input var name =
"Droidcon" @OutputFile val myFile: File = File(name) @TaskAction fun action() { println("Hello $name Karibu droidcon") myFile.createNewFile() myFile.writeText(fileText.get()) } } MyProject/my-plugin/src/main/kotlin/… -
abstract class SamplePlugin : Plugin<Project> { override fun apply(project: Project)
{ project.tasks.register<GreetingTask>("greet") { name = "Kalume" } } } MyProject/my-plugin/src/main/kotlin/… -
abstract class SamplePlugin : Plugin<Project> { override fun apply(project: Project)
{ project.tasks.register<GreetingTask>("greet") { name = "Kalume" } } } MyProject/my-plugin/src/main/kotlin/… -
abstract class SamplePlugin : Plugin<Project> { override fun apply(project: Project)
{ val extension = project.extensions.create<GreetingPluginExtension>("greetingConfig") project.tasks.register<GreetingTask>("greet") { name = extension.name.get() } } } MyProject/my-plugin/src/main/kotlin/… -
pluginManagement { includeBuild(“my-plugin”) repositories { google() gradlePluginPortal() mavenCentral() } }
MyProject/settings.gradle.kts -
plugin { id("ke.droidcon.greeting") } greetingConfig { name = "Kalume" }
./gradlew greet MyProject/app/build.gradle.kts -
Graph resolution phase • Gradle looks at all the declared
dependencies • Transitive dependencies • Constructs a resolved dependency graph -
Component A speci fi c version of a library kotlinx-serialization-json:1.7.1
kotlinx-serialization-json:1.6.0 Metadata what to know about that component. a .pom or .ivy fi le component’s dependencies available variants Attributes key-value pairs that describe characteristics of a variant E.g : org.gradle.jvm.version=17 Variants Each variant is designed for a di ff erent target platform or usage scenario. E.g : JVM, Kotlin/Native, JS or the context it’s being used in -
How gradle build the Dependency Graph My App Library A
1.3.2 Library C 1.0.2 Library B 1.2.3 Library C 2.0.4 My App Library A 1.3.2 Library B 1.2.3 Library C 2.0.4 -
./gradlew dependencies compileClasspath - Compile classpath for 'main'. + ---
org.jetbrains.kotlin:kotlin-stdlib:2.2.10 | \ --- org.jetbrains:annotations:13.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0 + --- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0 | + --- org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0 (c) | + --- org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0 (c) | + --- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 (c) | \ --- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0 (c) + --- org.jetbrains.kotlin:kotlin-stdlib:2.2.0 -> 2.2.10 (*) \ --- org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0 + --- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0(*) \ --- org.jetbrains.kotlin:kotlin-stdlib:2.2.0 -> 2.2.10 (*) -
./gradlew dependencies compileClasspath - Compile classpath for 'main'. + ---
org.jetbrains.kotlin:kotlin-stdlib:2.2.10 | \ --- org.jetbrains:annotations:13.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0 + --- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0 | + --- org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0 (c) | + --- org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0 (c) | + --- org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0 (c) | \ --- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0 (c) + --- org.jetbrains.kotlin:kotlin-stdlib:2.2.0 -> 2.2.10 (*) \ --- org.jetbrains.kotlinx:kotlinx-serialization-core:1.9.0 \ --- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0 + --- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0(*) \ --- org.jetbrains.kotlin:kotlin-stdlib:2.2.0 -> 2.2.10 (*) -
Artifact Resolution Gradle : “Alright, I know what I need;
now let’s go and get it.” • Contacts the repositories you’ve declared dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = uri("https: // yveskalume.dev") } } } -
Artifact Resolution Gradle : “Alright, I know what I need;
now let’s go and get it.” • Downloads these fi les into its local cache • Contacts the repositories you’ve declared -
Deamon • A long-lived background process • Executes your builds
much more quickly • Avoid the expensive bootstrapping process • Leverage caching, by keeping data about your project in memory. -
./path/to/local/distribution/bin/gradle <SomeTask> Gradle Client JVM Gradle Daemon Forward inputs Input
(command line args,…) Run the builds Outputs -
gradlew • Starts a tiny JVM using gradle/wrapper/gradle-wrapper.jar • That
JVM locates or downloads Gradle from gradle/wrapper/gradle- wrapper.properties • Uses Java re fl ection to start Gradle itself inside the same process • That tiny JVM essentially becomes the Gradle Client JVM -
What Can Go Wrong with the Gradle Daemon • Memory
leaks or corruption of global state • Builds that don’t release resources properly Solution • Kill the process manually • --no-daemon fl ag -
--no-daemon • The client JVM may convert itself into a
daemon JVM (if it is compatible with build requirements) • The build happens inside the single JVM • If not compatible a new disposable JVM will still be started for the build and exit at the end of the build -
• It’s detecting more than one Java version in your
environment. • Your Gradle JDK and your system JAVA_HOME point to di ff erent Java installations • Not dangerous, but it increases memory usage unnecessarily • Just make sure your Gradle JDK matches the JAVA_HOME “Multiple Gradle daemons might be spawned because the Gradle JDK and JAVA_HOME locations are di ff erent.” -
Remember about Tasks ? A task can actually be seen
like a pure function. Action Input Output -
Remember about Tasks ? A task can actually be seen
like a pure function. It can be can be deterministic Task 1 Input Output Task 2 Etc -
Gradle cache Local cache Remote cache In-memory (Deamon) in-project Out
of the project Remote Build cache Proxy Con fi guration cache Build folder Dependency cache Build cache -
Build folder • Execution phase • Analyze input and compare
with the most recent run • {project}/.gradle/version/executionHistory/ executionHistory.bin • Checks your module’s build/ folder and reuses those results • ./gradlew clean -
Dependency cache • ~/.gradle/caches/modules-2 • Stores jars, aars, and poms
fi les • Using a proxy for Maven repositories can be a good idea -
Build cache • Reuse task outputs from any earlier build
in any location on the local machine • ~/.gradle/caches/build-cache-1 • org.gradle.caching=true -
Build cache key Cache Key Source files -> Hash Compiler
args -> Hash Java version -> Hash ~/.gradle/caches/build-cache-1 -
Gradle caching Module build folder Task Input Task Build cache
Task Output FROM CACHE -
Gradle caching Module build folder Task Input Task Task Action
Dorg.gradle.caching.debug=true Cache Key Task Output Cached Cache misses -
Remote Build cache • Build cache can be saved remotely
• It’s recommended to generate cache from the CI -
Remote Build cache buildCache { remote<HttpBuildCache> { url = uri(“https:
// my-cache-server:8123/cache/“) } } -
Conclusion • Stop thinking of Gradle as a black box
• Treat your build script like real code • Don’t panic ! • Play with it