Por lo general, las aplicaciones para Android se compilan con el sistema de compilación de Gradle. Antes de profundizar en los detalles de cómo configurar tu compilación, exploraremos los conceptos detrás de ella para que puedas ver el sistema como un todo.
¿Qué es una compilación?
Un sistema de compilación transforma tu código fuente en una aplicación ejecutable. Las compilaciones suelen incluir varias herramientas para analizar, compilar, vincular y empaquetar tu aplicación o biblioteca. Gradle usa un enfoque basado en tareas para organizar y ejecutar estos comandos.
Las tareas encapsulan comandos que traducen sus entradas en
salidas. Los complementos definen las tareas y su configuración. Cuando aplicas
un complemento a tu compilación, se registran sus tareas y se conectan entre sí con sus
entradas y salidas. Por ejemplo, si aplicas el complemento de Android para Gradle (AGP) a tu archivo de compilación, se registrarán todas las tareas necesarias para compilar un APK o una biblioteca de Android. El complemento java-library te permite compilar un archivo JAR a partir del código fuente de Java. Existen complementos similares para Kotlin y otros lenguajes, pero otros complementos
están diseñados para extender complementos. Por ejemplo, el complemento protobuf está diseñado para agregar compatibilidad con protobuf a complementos existentes, como AGP o java-library.
Gradle prefiere la convención sobre la configuración, por lo que los complementos incluirán buenos valores predeterminados listos para usar, pero puedes configurar aún más la compilación a través de un lenguaje específico del dominio (DSL) declarativo. El DSL está diseñado para que puedas especificar qué compilar, en lugar de cómo hacerlo. La lógica de los complementos administra el "cómo". Esa configuración se especifica en varios archivos de compilación de tu proyecto (y subproyectos).
Las entradas de tareas pueden ser archivos y directorios, así como otra información codificada como tipos de Java (números enteros, cadenas o clases personalizadas). Los resultados solo pueden ser directorios o archivos, ya que se deben escribir en el disco. Conectar el resultado de una tarea a la entrada de otra vincula las tareas para que una se ejecute antes que la otra.
Si bien Gradle admite la escritura de código arbitrario y declaraciones de tareas en tus archivos de compilación, esto puede dificultar que las herramientas comprendan tu compilación y que tú la mantengas. Por ejemplo, puedes escribir pruebas para el código dentro de los complementos, pero no en los archivos de compilación. En su lugar, debes restringir la lógica de compilación y las declaraciones de tareas a los complementos (que tú o alguien más definan) y declarar cómo quieres usar esa lógica en tus archivos de compilación.
¿Qué sucede cuando se ejecuta una compilación de Gradle?
Las compilaciones de Gradle se ejecutan en tres fases. Cada una de estas fases ejecuta diferentes partes del código que defines en tus archivos de compilación.
- La inicialización determina qué proyectos y subproyectos se incluyen en la compilación y configura las rutas de clase que contienen tus archivos de compilación y los complementos aplicados. Esta fase se enfoca en un archivo de configuración en el que declaras los proyectos que se compilarán y las ubicaciones desde las que se recuperarán los complementos y las bibliotecas.
- La configuración registra las tareas de cada proyecto y ejecuta el archivo de compilación para aplicar la especificación de compilación del usuario. Es importante comprender que tu código de configuración no tendrá acceso a los datos ni a los archivos producidos durante la ejecución.
- La ejecución realiza la "compilación" real de tu aplicación. El resultado de la configuración es un grafo acíclico dirigido (DAG) de tareas, que representa todos los pasos de compilación requeridos que solicitó el usuario (las tareas proporcionadas en la línea de comandos o como valores predeterminados en los archivos de compilación). Este grafo representa la relación entre las tareas, ya sea de forma explícita en la declaración de una tarea o en función de sus entradas y salidas. Si una tarea tiene una entrada que es el resultado de otra tarea, debe ejecutarse después de la otra tarea. En esta fase, se ejecutan las tareas desactualizadas en el orden definido en el gráfico. Si las entradas de una tarea no cambiaron desde su última ejecución, Gradle la omitirá.
Para obtener más información, consulta el ciclo de vida de la compilación de Gradle.
DSL de configuración
Gradle usa un lenguaje específico del dominio (DSL) para configurar las compilaciones. Este enfoque declarativo se enfoca en especificar tus datos en lugar de escribir instrucciones paso a paso (imperativas). Puedes escribir tus archivos de compilación con Kotlin o Groovy, pero te recomendamos que uses Kotlin.
Los DSL intentan facilitar que todos, tanto los expertos en dominios como los programadores, contribuyan a un proyecto, definiendo un lenguaje pequeño que represente los datos de una manera más natural. Los complementos de Gradle pueden extender el DSL para configurar los datos que necesitan para sus tareas.
Por ejemplo, la configuración de la parte de Android de tu compilación podría verse de la siguiente manera:
Kotlin
android { namespace = "com.example.app" compileSdk { version = release(36) { minorApiLevel = 1 } } // ... defaultConfig { applicationId = "com.example.app" minSdk { version = release(23) } targetSdk { version = release(36) } // ... } }
Groovy
android { namespace = 'com.example.app' compileSdk { version = release(36) { minorApiLevel = 1 } } // ... defaultConfig { applicationId = 'com.example.app' minSdk { version = release(23) } targetSdk { version = release(36) } // ... } }
Tras bambalinas, el código DSL es similar al siguiente:
fun Project.android(configure: ApplicationExtension.() -> Unit) {
...
}
interface ApplicationExtension {
var namespace: String?
fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
...
}
val defaultConfig: DefaultConfig
fun defaultConfig(configure: DefaultConfig.() -> Unit) {
...
}
}
Cada bloque en el DSL se representa con una función que toma una expresión lambda para configurarlo y una propiedad con el mismo nombre para acceder a él. Esto hace que el código de tus archivos de compilación se parezca más a una especificación de datos.
Dependencias externas
El sistema de compilación de Maven introdujo un sistema de especificación, almacenamiento y administración de dependencias. Las bibliotecas se almacenan en repositorios (servidores o directorios), con metadatos que incluyen su versión y dependencias de otras bibliotecas. Debes especificar qué repositorios buscar y las versiones de las dependencias que deseas usar, y el sistema de compilación las descargará durante la compilación.
Los artefactos de Maven se identifican por el nombre del grupo (empresa, desarrollador, etc.), el nombre del artefacto (el nombre de la biblioteca) y la versión de ese artefacto. Por lo general, se representa como group:artifact:version.
Este enfoque mejora significativamente la administración de compilaciones. A menudo, escucharás que estos repositorios se llaman "repositorios de Maven", pero todo se trata de la forma en que se empaquetan y publican los artefactos. Estos repositorios y metadatos se reutilizaron en varios sistemas de compilación, incluido Gradle (y Gradle puede publicar en estos repositorios). Los repositorios públicos permiten compartir el código para que todos lo usen, y los repositorios de la empresa mantienen las dependencias internas de forma interna.
También puedes modularizar tu proyecto en subproyectos (también conocidos como "módulos" en Android Studio), que también se pueden usar como dependencias. Cada subproyecto produce resultados (como archivos JAR) que pueden consumir los subproyectos o tu proyecto de nivel superior. Esto puede mejorar el tiempo de compilación, ya que aísla las partes que se deben volver a compilar y separa mejor las responsabilidades en la aplicación.
Analizaremos con más detalle cómo especificar dependencias en Cómo agregar dependencias de compilación.
Variantes de compilación
Cuando creas una aplicación para Android, por lo general, querrás compilar varias variantes. Las variantes contienen código diferente o se compilan con opciones distintas, y se componen de tipos de compilación y variantes de productos.
Los tipos de compilación varían las opciones de compilación declaradas. De forma predeterminada, el AGP configura los tipos de compilación "release" y "debug", pero puedes ajustarlos y agregar más (por ejemplo, para pruebas internas o de etapa de pruebas).
Una compilación de depuración no reduce ni ofusca tu aplicación, lo que acelera su compilación y conserva todos los símbolos tal como están. También marca la aplicación como "depurable", la firma con una clave de depuración genérica y habilita el acceso a los archivos de la aplicación instalados en el dispositivo. Esto permite explorar los datos guardados en archivos y bases de datos mientras se ejecuta la aplicación.
Una compilación de lanzamiento optimiza la aplicación, la firma con tu clave de lanzamiento y protege los archivos de la aplicación instalada.
Con las variantes de productos, puedes cambiar las variantes de dependencias y fuentes incluidas para la aplicación. Por ejemplo, puedes crear variantes de "demostración" y "completa" para tu aplicación, o bien variantes "gratuita" y "pagada". Escribes tu código fuente común en un directorio de conjunto de orígenes "principal" y anulas o agregas código fuente en un conjunto de orígenes que lleva el nombre de la variante.
El AGP crea variantes para cada combinación de tipo de compilación y variante de producto. Si no defines variantes, las variantes de compilación se nombrarán según los tipos de compilación. Si defines ambas, la variante se llamará <flavor><Buildtype>. Por ejemplo, con los tipos de compilación release y debug, y las variantes demo y full, el AGP creará las siguientes variantes:
demoReleasedemoDebugfullReleasefullDebug
Próximos pasos
Ahora que conoces los conceptos de compilación, consulta la estructura de compilación de Android en tu proyecto.