El complemento de Android para Gradle (AGP) es el sistema de compilación compatible con aplicaciones para Android. Además, admite la compilación de muchos tipos diferentes de fuentes y su vinculación a una aplicación que puedes ejecutar en un dispositivo Android físico o en un emulador.
AGP contiene puntos de extensión para que los complementos controlen las entradas de compilación y extiendan su funcionalidad mediante pasos nuevos que se pueden integrar con tareas de compilación estándar. Las versiones anteriores del AGP no tenían API oficiales separadas claramente de implementaciones internas. A partir de la versión 7.0, AGP tiene un conjunto de API oficiales y estables en las que puedes confiar.
Ciclo de vida de las API de AGP
AGP sigue el ciclo de vida de las funciones de Gradle para designar el estado de sus API:
- Interna: No está diseñada para uso público.
- En preparación: Una versión que no es la final, pero que se encuentra disponible para uso público, lo que significa que puede que no tenga retrocompatibilidad en la versión final.
- Pública: Disponible para uso público y estable.
- Obsoleta: Ya no es compatible y se reemplazó con API nuevas.
Política de baja
AGP está avanzando con la baja de las API anteriores y su reemplazo con API nuevas y estables, además de un nuevo lenguaje específico de dominio (DSL). Esta evolución abarcará varias versiones del AGP. Obtén más información al respecto en el cronograma de migración del DSL y la API de AGP.
Cuando las API de AGP dejen de estar disponibles para esta migración o en otros casos, seguirán estando disponibles en la versión principal actual, pero generarán advertencias. Las API obsoletas se quitarán por completo de AGP en la versión principal posterior. Por ejemplo, si una API deja de estar disponible en AGP 7.0, estará disponible en esa versión y generará advertencias. Esa API ya no estará disponible en AGP 8.0.
Para ver ejemplos de API nuevas que se usan en personalizaciones de compilación comunes, consulta las recetas de complemento de Android para Gradle. Allí, se proporcionan ejemplos de personalizaciones de compilación comunes. También puedes obtener más detalles sobre las API nuevas en nuestra documentación de referencia.
Conceptos básicos de compilación de Gradle
En esta guía, no se abarca todo el sistema de compilación de Gradle. Sin embargo, sí se incluye el conjunto mínimo de conceptos necesarios para ayudarte a integrar nuestras API, además de vínculos a la documentación principal de Gradle para consultas adicionales.
Ten en cuenta que se requieren conocimientos básicos sobre el funcionamiento de Gradle, como configuración de proyectos, edición archivos de compilación, uso de complementos y ejecución de tareas. Para conocer los conceptos básicos de Gradle en relación con el AGP, te recomendamos que revises el artículo para configurar tu compilación. Si quieres obtener más información sobre el framework general para personalizar complementos de Gradle, consulta Developing Custom Gradle Plugins (Cómo desarrollar complementos de Gradle personalizados).
Glosario de tipos diferidos de Gradle
Gradle ofrece varios tipos que se comportan "de manera diferida" o ayudan a diferir los cálculos pesados o la creación de Task
a fases posteriores de la compilación. Estos tipos son centrales en muchas API de Gradle y AGP. En la siguiente lista se incluyen los tipos principales de Gradle que se usan en la ejecución diferida y sus métodos clave.
Provider<T>
- Proporciona un valor de tipo
T
(en el que "T" significa cualquier tipo), que se puede leer durante la fase de ejecución conget()
o transformarse en unaProvider<S>
nueva (en la que "S" significa algún otro tipo) con los métodosmap()
,flatMap()
yzip()
. Ten en cuenta que nunca se debe llamar aget()
durante la fase de configuración.map()
: Acepta una lambda y produce unProvider
de tipoS
,Provider<S>
. El argumento lambda paramap()
toma el valorT
y produce el valorS
. La lambda no se ejecuta de inmediato. En cambio, su ejecución se difiere al momento en que se llama aget()
en elProvider<S>
resultante, lo que hace que toda la cadena sea diferida.flatMap()
: También acepta una lambda y produceProvider<S>
, pero la lambda toma un valorT
y produceProvider<S>
(en lugar de producir el valorS
directamente). Usa flatMap() cuando no se pueda determinar S en el momento de la configuración y solo puedas obtenerProvider<S>
. En términos prácticos, si usastemap()
y obtuviste un tipo de resultadoProvider<Provider<S>>
, es probable que debas usarflatMap()
en su lugar.zip()
: Te permite combinar dos instancias deProvider
para producir unProvider
nuevo, con un valor calculado mediante una función que combina los valores de las dos instancias deProviders
de entrada.
Property<T>
- Implementa
Provider<T>
, por lo que también brinda un valor de tipoT
. A diferencia de lo que ocurre conProvider<T>
, que es de solo lectura, también puedes establecer un valor para laProperty<T>
. Puedes hacerlo de la siguientes dos maneras:- Establecer un valor de tipo
T
directamente cuando esté disponible, sin la necesidad de realizar cálculos diferidos - Configurar otro
Provider<T>
como la fuente del valor de laProperty<T>
(en este caso, el valorT
se materializa solo cuando se llama aProperty.get()
)
- Establecer un valor de tipo
TaskProvider
- Implementa
Provider<Task>
. Para generar unTaskProvider
, usatasks.register()
y notasks.create()
a fin de asegurarte de que solo se creen instancias de manera diferida cuando sean necesarias. Puedes usarflatMap()
para acceder a los resultados de unTask
antes de que se creeTask
, lo que puede ser útil si deseas usar las salidas como entradas para otras instancias deTask
.
Los proveedores y sus métodos de transformación son esenciales para configurar entradas y salidas de tareas de manera diferida, es decir, sin necesidad de crear todas las tareas por adelantado y resolver los valores.
Los proveedores también tienen información sobre la dependencia de las tareas. Cuando creas un Provider
mediante la transformación de un resultado de Task
, ese Task
se convierte en una dependencia implícita de Provider
y se creará y ejecutará cada vez que se resuelva el valor de Provider
, como en caso de que otro Task
lo requiera.
A continuación, se muestra un ejemplo para registrar dos tareas, GitVersionTask
y ManifestProducerTask
, y diferir la creación de las instancias de Task
hasta que sean obligatorias. El valor de entrada de ManifestProducerTask
se establece en un Provider
que se obtuvo del resultado de GitVersionTask
, por lo que ManifestProducerTask
depende de manera implícita de GitVersionTask
.
// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
it.gitVersionOutputFile.set(
File(project.buildDir, "intermediates/gitVersionProvider/output")
)
}
...
/**
* Register another task in the configuration block (also executed lazily,
* only if the task is required).
*/
val manifestProducer =
project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
/**
* Connect this task's input (gitInfoFile) to the output of
* gitVersionProvider.
*/
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
Estas dos tareas solo se ejecutarán si se solicitan de explícitamente. Esto puede suceder como parte de una invocación de Gradle, por ejemplo, si ejecutas ./gradlew
debugManifestProducer
o si el resultado de ManifestProducerTask
está conectado a alguna otra tarea y se requiere su valor.
Aunque en el futuro escribas tareas personalizadas que consuman entradas o produzcan resultados, AGP no ofrece acceso público a sus propias tareas directamente. Son un detalle de implementación sujeto a cambios de versión a versión. En su lugar, AGP ofrece la API de variantes y acceso al resultado de sus tareas (o artefactos de compilación) que puedes leer y transformar. Para obtener más información, consulta API de variantes, artefactos y tareas en este documento.
Fases de la compilación de Gradle
La compilación de un proyecto es inherentemente un proceso complicado que requiere muchos recursos. Sin embargo, hay varias funciones, como la elusión de la configuración de tareas, las verificaciones actualizadas y la función de almacenamiento en caché de configuración, que ayudan a minimizar el tiempo invertido en cálculos reproducibles o innecesarios.
Para aplicar algunas de estas optimizaciones, las secuencias de comandos y los complementos de Gradle deben cumplir con reglas estrictas durante cada una de las distintas fases de compilación de Gradle: inicialización, configuración y ejecución. En esta guía, nos enfocaremos en las fases de configuración y ejecución. Puedes encontrar más información sobre todas las fases en la guía del ciclo de vida de compilación de Gradle.
Fase de configuración
Durante la fase de configuración, se evalúan las secuencias de comandos de compilación de todos los proyectos que forman parte de la compilación, se aplican los complementos y se resuelven las dependencias de compilación. Esta fase debe usarse para configurar la compilación mediante objetos DSL y para registrar tareas y sus entradas de manera diferida.
Debido a que la fase de configuración se ejecuta siempre, independientemente de la tarea que se solicita para ejecución, es muy importante que sea eficiente y evitar que los cálculos dependan de entradas que no sean las secuencias de comandos de compilación.
Es decir, no debes ejecutar programas externos ni leer desde la red ni realizar cálculos largos que se puedan diferir a la fase de ejecución como instancias de Task
adecuadas.
Fase de ejecución
En la fase de ejecución, se ejecutan las tareas solicitadas y las tareas dependientes. En concreto, se ejecutan los métodos de clase de Task
marcados con @TaskAction
. Durante la ejecución de las tareas, si llamas a Provider<T>.get()
, puedes leer las entradas (como los archivos) y resolver los proveedores diferidos. Cuando se resuelven los proveedores diferidos, se inicia una secuencia de llamadas map()
o flatMap()
que siguen la información de dependencia de las tareas incluida en el proveedor. Las tareas se ejecutan de manera diferida para materializar los valores requeridos.
API de variantes, artefactos y tareas
La API de variantes es un mecanismo de extensión en el complemento de Android para Gradle que te permite manipular las distintas opciones, que suelen establecerse con DSL en los archivos de configuración de compilación que influyen en la compilación de Android. La API de variantes también te brinda acceso a artefactos intermedios y finales que crea la compilación, como los archivos de clase, el manifiesto combinado o los archivos APK/AAB.
Flujo de compilación y puntos de extensión de Android
Cuando interactúas con AGP, usa sus puntos de extensión creados en lugar de registrar las devoluciones típicas de llamada de ciclo de vida de Gradle (como afterEvaluate()
) o configurar dependencias explícitas de Task
. Las tareas creadas por AGP se consideran detalles de la implementación y no se exponen como una API pública. Evita obtener instancias de los objetos Task
o adivinar los nombres de Task
y agregar devoluciones de llamada o dependencias a esos objetos de Task
directamente.
AGP completa los siguientes pasos para crear y ejecutar sus instancias de Task
, que, a su vez, producen los artefactos de compilación. Después de los pasos principales que se incluyen en la creación de objetos Variant
, siguen las devoluciones de llamada que te permiten realizar cambios en ciertos objetos creados como parte de una compilación. Es importante tener en cuenta que todas las devoluciones de llamada ocurren durante la fase de configuración (que se describe en esta página) y que deben ejecutarse rápido, de forma tal que se difiera cualquier trabajo complicado en las instancias de Task
adecuadas durante la fase de ejecución.
- Análisis de DSL: Aquí se evalúan las secuencias de comandos de compilación y se crean y configuran las distintas propiedades de los objetos DSL de Android del bloque
android
. Las devoluciones de llamada de la API de variantes que se describen en las siguientes secciones también se registran durante esta fase. finalizeDsl()
: Es la devolución de llamada que te permite cambiar objetos DSL antes de que se bloqueen para la creación de componentes (variantes). Los objetosVariantBuilder
se crean a partir de datos contenidos en los objetos DSL.Bloqueo de DSL: DSL está bloqueado y ya no es posible hacer cambios.
beforeVariants()
: Esta devolución de llamada puede influir en los componentes que se crean y en algunas de sus propiedades, por medio deVariantBuilder
. Aun así, permite modificaciones en el flujo de compilación y en los artefactos que se producen.Creación de variantes: Se completó la lista de componentes y artefactos que se crearán y no se puede cambiar.
onVariants()
: En esta devolución de llamada, obtendrás acceso a los objetosVariant
creados y puedes establecer valores o proveedores de los valores deProperty
que contienen para que se calculen de manera diferida.Bloqueo de variantes: Los objetos de variantes ahora están bloqueados y ya no es posible realizar cambios.
Tareas creadas: Los objetos
Variant
y sus valoresProperty
se usan para crear las instancias deTask
que son necesarias a fin de ejecutar la compilación.
AGP introduce una AndroidComponentsExtension
que te permite registrar devoluciones de llamada para finalizeDsl()
, beforeVariants()
y onVariants()
.
La extensión está disponible en las secuencias de comandos de compilación por medio del bloque androidComponents
:
// This is used only for configuring the Android build through DSL.
android { ... }
// The androidComponents block is separate from the DSL.
androidComponents {
finalizeDsl { extension ->
...
}
}
Sin embargo, recomendamos mantener las secuencias de comandos de compilación solo para la configuración declarativa usando el DSL del bloque de Android y mover la lógica imperativa personalizada a buildSrc
o a complementos externos. También puedes consultar las muestras de buildSrc
en el repositorio de GitHub de recetas de Gradle para aprender a crear un complemento en tu proyecto. A continuación, se muestra un ejemplo para registrar devoluciones de llamada desde el código del complemento:
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
...
}
}
}
Analicemos con más detalle las devoluciones de llamada disponibles y el tipo de casos de uso que tu complemento puede admitir en cada una de ellas:
finalizeDsl(callback: (DslExtensionT) -> Unit)
En esta devolución de llamada, puedes acceder a los objetos DSL que se crearon y modificarlos mediante el análisis de la información del bloque android
en los archivos de compilación.
Estos objetos DSL se usarán para inicializar y configurar variantes en las fases posteriores de la compilación. Por ejemplo, puedes crear configuraciones nuevas de manera programática o anular propiedades. Sin embargo, ten en cuenta que todos los valores deben resolverse en el momento de la configuración, por lo que no deben depender de las entradas externas.
Una vez que se termina de ejecutar esta devolución de llamada, los objetos DSL ya no son útiles y ya no debes tener referencias a ellos ni modificar sus valores.
abstract class ExamplePlugin: Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.finalizeDsl { extension ->
extension.buildTypes.create("extra").let {
it.isJniDebuggable = true
}
}
}
}
beforeVariants()
En esta etapa de la compilación, obtendrás acceso a objetos VariantBuilder
, que determinan las variantes que se crearán y sus propiedades. Por ejemplo, puedes inhabilitar de manera programática determinadas variantes y sus pruebas o cambiar el valor de una propiedad (por ejemplo, minSdk
) solo para una variante seleccionada. De manera similar a finalizeDsl()
, todos los valores que proporcionas deben resolverse en el momento de la configuración y no depender de entradas externas. Los objetos VariantBuilder
no deben modificarse una vez que termina la ejecución de la devolución de llamada de beforeVariants()
.
androidComponents {
beforeVariants { variantBuilder ->
variantBuilder.minSdk = 23
}
}
La devolución de llamada de beforeVariants()
, de forma opcional, toma un VariantSelector
, que puedes obtener por medio del método de selector()
en la androidComponentsExtension
. Puedes usarla para filtrar los componentes que participan en la invocación de devolución de llamada en función del nombre, el tipo de compilación o la variante de producto.
androidComponents {
beforeVariants(selector().withName("adfree")) { variantBuilder ->
variantBuilder.minSdk = 23
}
}
onVariants()
Antes de llamar a onVariants()
, ya se decidieron todos los artefactos que creará AGP, por lo que ya no puedes inhabilitarlos. Sin embargo, puedes modificar algunos de los valores que se usan para las tareas si los configuras en los atributos Property
en los objetos Variant
. Debido a que los valores Property
solo se resolverán cuando se ejecuten las tareas del AGP, puedes vincularlos de forma segura con los proveedores de tus propias tareas personalizadas que realizarán cualquier cálculo requerido, incluida la lectura desde entradas externas como archivos o la red.
// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
// Gather the output when we are in single mode (no multi-apk).
val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }
// Create version code generating task
val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
}
/**
* Wire version code from the task output.
* map() will create a lazy provider that:
* 1. Runs just before the consumer(s), ensuring that the producer
* (VersionCodeTask) has run and therefore the file is created.
* 2. Contains task dependency information so that the consumer(s) run after
* the producer.
*/
mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}
Cómo aportar fuentes generadas a la compilación
Tu complemento puede aportar algunos tipos de fuentes generadas, como las siguientes:
- Código de la aplicación en el directorio
java
- Recursos de Android en el directorio
res
- Recursos de Java en el directorio
resources
- Recursos de Android en el directorio
assets
Para obtener la lista completa de fuentes que puedes agregar, consulta la API de Sources.
En este fragmento de código, se muestra cómo agregar una carpeta personalizada de fuentes llamada ${variant.name}
al conjunto de orígenes de Java con la función addStaticSourceDirectory()
. La cadena de herramientas de Android luego procesa esta carpeta.
onVariants { variant ->
variant.sources.java?.let { java ->
java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
}
}
Consulta la receta de addJavaSource para obtener más detalles.
En este fragmento de código, se muestra cómo agregar un directorio con recursos de Android generados a partir de una tarea personalizada al conjunto de orígenes res
. El proceso es similar para otros tipos de fuentes.
onVariants(selector().withBuildType("release")) { variant ->
// Step 1. Register the task.
val resCreationTask =
project.tasks.register<ResCreatorTask>("create${variant.name}Res")
// Step 2. Register the task output to the variant-generated source directory.
variant.sources.res?.addGeneratedSourceDirectory(
resCreationTask,
ResCreatorTask::outputDirectory)
}
...
// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
@get:OutputFiles
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun taskAction() {
// Step 4. Generate your resources.
...
}
}
Consulta la receta de addCustomAsset para obtener más detalles.
Acceso y modificación de artefactos
Además de permitirte modificar propiedades simples en los objetos Variant
, AGP también contiene un mecanismo de extensión que te permite leer o transformar artefactos intermedios y finales que se produjeron durante la compilación. Por ejemplo, puedes leer el contenido combinado final del archivo AndroidManifest.xml
en una Task
personalizada para analizarlo o puedes reemplazar todo su contenido por el de un archivo de manifiesto generado por tu Task
personalizada.
Puedes encontrar la lista de artefactos que se admiten actualmente en la documentación de referencia de la clase Artifact
. Cada tipo de artefacto tiene propiedades determinadas que son útiles para conocer lo siguiente:
Cardinalidad
La cardinalidad de un Artifact
representa la cantidad de instancias de FileSystemLocation
o la cantidad de archivos o directorios del tipo de artefacto. Puedes obtener información sobre la cardinalidad de un artefacto si verificas su clase superior. Los artefactos con una sola FileSystemLocation
serán una subclase de Artifact.Single
; los artefactos con varias instancias de FileSystemLocation
serán una subclase de Artifact.Multiple
.
Tipo de FileSystemLocation
Para verificar si un Artifact
representa archivos o directorios, consulta el tipo de FileSystemLocation
parametrizada, que puede ser un RegularFile
o un Directory
.
Operaciones admitidas
Cada clase de Artifact
puede implementar cualquiera de las siguientes interfaces para indicar el tipo de operaciones que admite:
Transformable
: Permite usar un objetoArtifact
como entrada para un objetoTask
que realiza transformaciones arbitrarias en él y genera una versión nueva del objetoArtifact
.Appendable
: Se aplica solo a los artefactos que son subclases deArtifact.Multiple
. Significa queArtifact
se puede agregar, es decir, unaTask
personalizada puede crear instancias nuevas de este tipo deArtifact
que se agregarán a la lista existente.Replaceable
: Se aplica solo a los artefactos que son subclases deArtifact.Single
. UnArtifact
reemplazable se puede reemplazar por una instancia completamente nueva, producida como resultado de unaTask
.
Además de las tres operaciones de modificación de artefactos, cada artefacto admite una operación get()
(o getAll()
), que muestra un Provider
con la versión final del artefacto (después de que se completan todas las operaciones).
Varios complementos pueden agregar cualquier cantidad de operaciones en artefactos a la canalización desde la devolución de llamada onVariants()
. Además, AGP garantizará que se encadenen adecuadamente para que todas las tareas se ejecuten en el momento adecuado y los artefactos estén actualizados y se produzcan de forma correcta. Esto significa que, cuando una operación cambia los resultados mediante anexos, reemplazos o transformaciones, la próxima operación verá la versión actualizada de estos artefactos como entradas, y así sucesivamente.
El punto de entrada para registrar operaciones es la clase Artifacts
.
En el siguiente fragmento de código, se muestra el modo en el que puedes obtener acceso a una instancia de Artifacts
desde una propiedad en el objeto Variant
de la devolución de llamada onVariants()
.
Luego, puedes pasar tu TaskProvider
personalizado para obtener un objeto TaskBasedOperation
(1) y usarlo a fin de conectar sus entradas y salidas con uno de los métodos wiredWith*
(2).
El método exacto que debes elegir depende de la cardinalidad y el tipo de FileSystemLocation
implementado por el Artifact
que deseas transformar.
Y, por último, pasa el tipo de Artifact
a un método que represente la operación elegida en el objeto *OperationRequest
que obtienes como resultado, por ejemplo, toAppendTo()
, toTransform()
o toCreate()
(3).
androidComponents.onVariants { variant ->
val manifestUpdater = // Custom task that will be used for the transform.
project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
}
// (1) Register the TaskProvider w.
val variant.artifacts.use(manifestUpdater)
// (2) Connect the input and output files.
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest)
// (3) Indicate the artifact and operation type.
.toTransform(SingleArtifact.MERGED_MANIFEST)
}
En este ejemplo, MERGED_MANIFEST
es un SingleArtifact
y es un RegularFile
. Debido a eso, debemos usar el método wiredWithFiles
, que acepta una sola referencia de RegularFileProperty
para la entrada y una sola RegularFileProperty
para la salida. Existen otros métodos de wiredWith*
en la clase TaskBasedOperation
que funcionarán para otras combinaciones de cardinalidad de Artifact
y tipos de FileSystemLocation
.
Para obtener más información a fin de extender el AGP, te recomendamos que leas las siguientes secciones del manual del sistema de compilación de Gradle:
- Developing Custom Gradle Plugins (Cómo desarrollar complementos personalizados de Gradle)
- Implementing Gradle plugins (Cómo implementar complementos de Gradle)
- Developing Custom Gradle Task Types (Cómo desarrollar tipos de tareas personalizadas de Gradle)
- Lazy Configuration (Configuración diferida)
- Task Configuration Avoidance (Elusión de configuración de tareas)