Descripción general de la compilación de Gradle

Las aplicaciones para Android suelen compilarse con el sistema de compilación Gradle. Antes de profundizar en los detalles de cómo configurar tu compilación, exploraremos los conceptos detrás de la compilación para que puedas ver el sistema en su totalidad.

¿Qué es una compilación?

Un sistema de compilación transforma tu código fuente en una aplicación ejecutable. Las compilaciones suelen involucrar 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 tareas y su configuración. Cuando aplicas un complemento a tu compilación, se registran sus tareas y se conectan entre sí a través de sus entradas y salidas. Por ejemplo, aplicar el complemento de Gradle para Android (AGP) a tu archivo de compilación registrará 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 a 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 en 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). Las salidas solo pueden ser directorios o archivos, ya que deben escribirse en el disco. Conectar el resultado de una tarea a la entrada de otra vincula las tareas de modo que una debe ejecutarse 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 cambio, 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 deseas 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 acceso a clases que contienen los archivos de compilación y los complementos aplicados. En esta fase, se trabaja con un archivo de configuración en el que se declaran los proyectos que se compilarán y las ubicaciones desde las que se recuperarán los complementos y las bibliotecas.
  • Configuration registra tareas para 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 necesarios que solicitó el usuario (las tareas proporcionadas en la línea de comandos o como valores predeterminados en los archivos de compilación). Este gráfico representa la relación entre las tareas, ya sea explícita en la declaración de una tarea o basada en sus entradas y salidas. Si una tarea tiene una entrada que es la salida 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 compilación de Gradle.

DSL de configuración

Gradle usa un lenguaje específico del dominio (DSL) para configurar compilaciones. Este enfoque declarativo se centra 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 la contribución de todos, tanto expertos en el dominio como programadores, a un proyecto, ya que definen un lenguaje pequeño que representa 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, configurar 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)
        }
        // ...
    }
}

En segundo plano, el código de 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 del DSL se representa con una función que toma una lambda para configurarlo y una propiedad con el mismo nombre para acceder a él. Esto hace que el código de los archivos de compilación se parezca más a una especificación de datos.

Dependencias externas

El sistema de compilación de Maven introdujo una especificación, un sistema de almacenamiento y un sistema de 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. Especificas qué repositorios buscar, las versiones de las dependencias que deseas usar y el sistema de compilación las descarga durante la compilación.

Los artefactos de Maven se identifican por el nombre del grupo (empresa, desarrollador, etcétera), 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 denominan "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 que todos los usuarios compartan y usen los repositorios, y los repositorios de la empresa mantienen las dependencias internas dentro de la empresa.

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 consumirse en otros subproyectos o en tu proyecto de nivel superior. Esto puede mejorar el tiempo de compilación, ya que aísla las partes que deben recompilarse, además de separar mejor las responsabilidades en la aplicación.

Explicaremos 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 diferentes opciones, y se componen de tipos de compilación y variantes de producto.

Los tipos de compilación varían las opciones de compilación declaradas. De forma predeterminada, AGP configura los tipos de compilación "release" y "debug", pero puedes ajustarlos y agregar más (quizás para pruebas internas o de etapa de pruebas).

Una compilación de depuración no minimiza 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 instalada 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 producto, puedes cambiar el código fuente incluido y las variantes de dependencia de la aplicación. Por ejemplo, puedes crear variantes "demo" y "completa" para tu aplicación, o quizás variantes "gratis" y "pagada". Escribes tu fuente común en un directorio de conjunto de orígenes "principal" y anulas o agregas la fuente en un conjunto de orígenes con 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, estas se denominan según los tipos de compilación. Si defines ambos, la variante se llamará <flavor><Buildtype>. Por ejemplo, con los tipos de compilación release y debug, y las variantes demo y full, AGP creará las siguientes variantes:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Próximos pasos

Ahora que conoces los conceptos de compilación, consulta la estructura de compilación de Android en tu proyecto.