Tasks are an essential and complex topic for anyone working with Gradle. This section focuses on simple task creation and straightforward extensions based on Gradle’s default tasks or AGP Variant API. It does not cover advanced topics like lazy parameters, task dependency graphs, or complex plugins and tasks. Before diving in, it’s helpful to review Gradle’s official documentation for a foundational understanding:
With the recent development of Gradle’s lazy APIs, tasks can now be created lazily or immediately. The create()
API immediately executes task creation and configuration, while register()
defers it until the task is confirmed to be executed. Generally, the lazy creation API (register()
) is preferred, reducing unnecessary performance overhead during the Configuration phase. Understand the differences between these two through the following resources:
While create()
returns a Task
, register()
yields a TaskProvider
. If you need to access Task
APIs not available in TaskProvider
, you can use the configure()
method:
Apart from native API creation, it’s also essential to know the delegating API creation method, which is more common in build.gradle.kts
scripts and other script plugins:
Usually, when extending tasks, it’s common to create a custom task class that extends DefaultTask
. The most critical aspect of custom tasks is declaring inputs and outputs, as they influence cacheability and task graph computation:
For simple tasks like copying a file, you can directly create a Gradle built-in task:
For interaction with other plugins, it often involves configuring built-in tasks from these plugins:
tasks.named('generateDocumentation').configure {
// configuration code ...
}
Task actions are the actual carriers of task logic. There are two primary ways to add task actions. The first is by annotating methods within the task with @TaskAction
:
Task actions annotated with @TaskAction
are automatically detected and executed, but their order is not guaranteed.
The second method, doFirst()
/ doLast()
, is not for ordering but for quickly adding tasks or extending pre/post tasks for existing ones:
doFirst()
/ doLast()
should be lightweight, especially as supplements to existing tasks; more complex extensions should be handled with new tasks.
Task dependencies in a Gradle project form a Directed Acyclic Graph. To manually adjust task dependencies:
dependsOn(...)
API is used to add forward dependencies, transform backward dependencies into forward ones, insert tasks at the beginning or end, etc.shouldRunAfter(...)
and mustRunAfter()
are used for tasks without direct dependencies.finalizedBy
as a backward dependency API; it’s meant to add a finalizer task, similar to a try...catch...
scenario.Find more from below resources:
1, 2, 3 are introductions to Task dependencies and dependsOn
, 4 is a practical use case. Scenarios for automatically organizing Task dependencies can refer to the use of the AGP Variant API below, based on the Provider
mechanism.
Prior to version 7.0, the Android Gradle Plugin (AGP) lacked a defined API for third-party developers, necessitating direct examination of AGP’s Task source code to extend its functionality. This involved identifying specific input and output interception points for manipulation. Post version 7.0, the Android team has provided standard entry points for third-party plugin developers:
AGP includes plugin extension points to control build inputs and enhance its functionality through new steps that integrate with standard build tasks. Older AGP versions lacked an official API separate from its internal implementation. From version 7.0 onward, AGP will offer a set of stable official APIs that are reliable and trustworthy.
This excerpt, taken from the official documentation updated in late October 2021 (link 1), introduces the Variant API of AGP and its modular design for the first time. The document also details the Variant API’s process, extension points, and how to access and modify AGP-produced Artifacts. To further understand this API’s categorization and underlying design principles, refer to the “New Variant API” section in link 4, which consolidates content related to the Variant API from the 2020 DevSubmit and 2021 Google I/O. The code in link 2, maintained by the AGP official team, provides Variant API Samples for reference (and copying), such as the current process for obtaining the final output APK:
// https://github.com/android/gradle-recipes/blob/agp-7.0/Kotlin/getApksTest/app/build.gradle.kts#L26
abstract class DisplayApksTask: DefaultTask() {
@get:InputFiles
abstract val apkFolder: DirectoryProperty
@get:Internal
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
@TaskAction
fun taskAction() {
val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
?: throw RuntimeException("Cannot load APKs")
builtArtifacts.elements.forEach {
println("Got an APK at ${it.outputFile}")
}
}
}
androidComponents {
onVariants { variant ->
project.tasks.register<DisplayApksTask>("${variant.name}DisplayApks") {
apkFolder.set(variant.artifacts.get(SingleArtifact.APK))
builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())
}
}
}
doLast(...)
and doFirst()
combined with a Script Plugin for simple wrapping.