Cómo optimizar tu velocidad de compilación

Los tiempos de compilación prolongados ralentizan el proceso de desarrollo. En esta página, se proporcionan algunas técnicas para ayudar a resolver cuellos de botella en la velocidad de compilación.

El proceso general de mejora de la velocidad de compilación de la app es el siguiente:

  1. Optimiza la configuración de tu compilación mediante algunos pasos que proporcionan beneficios inmediatos para la mayoría de los proyectos de Android Studio.
  2. Generar un perfil para tu compilación a fin de identificar y diagnosticar algunos de los cuellos de botella más complejos que pueden ser específicos de tu proyecto o estación de trabajo

Cuando desarrollas tu app, debes implementar un dispositivo con Android 7.0 (API nivel 24) o una versión posterior siempre que sea posible. En las versiones más recientes de la plataforma de Android, se implementaron mejores mecanismos para introducir actualizaciones en tu app, como Android Runtime (ART) y la compatibilidad nativa con varios archivos DEX.

Nota: Después de tu primera compilación limpia, tal vez observes que las compilaciones posteriores, limpias e incrementales, son mucho más rápidas (aun cuando no se use ninguna de las optimizaciones que se describen en esta página). Esto se debe a que el daemon de Gradle tiene un período de "calentamiento" en el que aumenta el rendimiento, fenómeno similar al de otros procesos de equipos virtuales Java (JVM).

Optimizar la configuración de tu compilación

Sigue estas sugerencias para mejorar la velocidad de compilación de tu proyecto de Android Studio.

Cómo mantener tus herramientas actualizadas

Las herramientas de Android reciben optimizaciones de compilación y funciones nuevas prácticamente con todas las actualizaciones. En algunas sugerencias de esta página, se presupone que usas la versión más reciente. Para aprovechar las últimas optimizaciones, mantén actualizado lo siguiente:

Usa KSP en lugar de kapt

La Herramienta de procesamiento de anotaciones de Kotlin (kapt) es mucho más lenta que el Procesador de símbolos de Kotlin (KSP). Si escribes la fuente de Kotlin anotada y usas herramientas que procesan anotaciones (como Room) que admiten KSP, te recomendamos que migres a KSP.

Evitar compilar recursos innecesarios

Evita compilar y empaquetar recursos que no vayas a probar, como localizaciones de idioma adicionales y recursos de densidad de pantalla. En cambio, solo especifica un recurso de idioma y una densidad de pantalla para la variante "dev", como se muestra en el siguiente ejemplo:

Groovy

android {
    ...
    productFlavors {
        dev {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Experimenta colocando el Portal de complementos de Gradle en último lugar

En Android, todos los complementos se encuentran en los repositorios google() y mavenCentral(). Sin embargo, es posible que tu compilación necesite complementos de terceros que se resuelvan mediante el servicio gradlePluginPortal().

Gradle busca repositorios en el orden en que se declaran, por lo que el rendimiento de compilación se optimiza si los repositorios enumerados primero contienen la mayoría de los complementos. Por lo tanto, experimenta con la entrada gradlePluginPortal() colocándola en el último bloque del repositorio de tu archivo settings.gradle. En la mayoría de los casos, esto minimiza la cantidad de búsquedas redundantes de complementos y mejora la velocidad de compilación.

Para obtener más información sobre cómo Gradle navega por varios repositorios, consulta Declaring multiple repositories (en inglés) en la documentación de Gradle.

Usar valores de configuración de compilación estáticos con tu compilación de depuración

Usa siempre valores estáticos para las propiedades que se incluyen en el archivo de manifiesto o los archivos de recursos para tu tipo de compilación de depuración.

El uso de códigos de versión dinámicos, nombres de versión, recursos o cualquier otra lógica de compilación que modifique el archivo de manifiesto requiere una compilación de app completa cada vez que deseas ejecutar un cambio, aunque en otras circunstancias la modificación solo hubiera requerido un intercambio directo. Si tu configuración de compilación requiere esas propiedades dinámicas, debes aislarlas de las variantes de tu compilación de lanzamiento y conservar valores estáticos para tus compilaciones de depuración, como se muestra en el siguiente ejemplo:

  ...
  // Use a filter to apply onVariants() to a subset of the variants.
  onVariants(selector().withBuildType("release")) { variant ->
      // Because an app module can have multiple outputs when using multi-APK, versionCode
      // is only available on the variant output.
      // Gather the output when we are in single mode and there is no multi-APK.
      val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

      // Create the version code generating task.
      val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
          it.outputFile.set(project.layout.buildDirectory.file("versionCode${variant.name}.txt"))
      }

      // Wire the 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.flatMap { it.outputFile.map { it.asFile.readText().toInt() } })
  }
  ...

  abstract class VersionCodeTask : DefaultTask() {

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun action() {
        outputFile.get().asFile.writeText("1.1.1")
    }
  }

Consulta la receta de setVersionsFromTask en GitHub para aprender a establecer un código de versión dinámico en tu proyecto.

Usar versiones de dependencias estáticas

Cuando declares dependencias en tus archivos build.gradle, evita usar números de versión dinámicos (los que tienen un signo más al final, como 'com.android.tools.build:gradle:2.+'). El uso de números de versión dinámicos puede causar actualizaciones de versión inesperadas, dificultades en la resolución de diferencias de versión y compilaciones más lentas a raíz de que Gradle busca actualizaciones. En su lugar, usa números de versión estáticos.

Crear módulos de biblioteca

Busca en tu app código que puedas convertir en un módulo de biblioteca de Android. La modularización de tu código de esta manera permite que el sistema de compilación compile solo los módulos que modifiques y almacene en caché los resultados para compilaciones futuras. También aporta más eficacia a la ejecución de proyectos paralelos cuando habilitas esa optimización.

Crear tareas para lógica de compilación personalizada

Una vez que crees un perfil de compilación, si el perfil de compilación muestra que una parte relativamente larga del tiempo de compilación se dedica a la fase de **configuración de proyectos**, revisa tu secuencia de comandos build.gradle y busca código para incluir en una tarea de Gradle personalizada. Si se traslada parte de la lógica de compilación a una tarea, ayudarás a garantizar que se ejecute solo cuando sea necesario, los resultados se puedan almacenar en caché para compilaciones futuras y la lógica de compilación reúna las condiciones para ejecutarse en paralelo si habilitas la ejecución de proyectos en paralelo. Para obtener más información sobre las tareas para la lógica de compilación personalizada, lee la documentación oficial de Gradle.

Sugerencia: Si tu compilación incluye una gran cantidad de tareas personalizadas, te recomendamos ordenar tus archivos build.gradle creando clases de tareas personalizadas. Agrega tus clases al directorio project-root/buildSrc/src/main/groovy/; Gradle las incluirá automáticamente en la ruta de clase para todos los archivos build.gradle de tu proyecto.

Cómo convertir imágenes a WebP

WebP es un formato de archivo de imagen que proporciona compresión con pérdidas (como JPEG) y transparencia (como PNG). WebP puede proporcionar una mejor compresión que los archivos JPEG o PNG.

Reducir los tamaños de los archivos de imagen sin necesidad de realizar compresión de tiempo de compilación puede acelerar tus compilaciones, en particular, si tu app usa una gran cantidad de recursos de imagen. No obstante, puedes observar un pequeño incremento en el uso de la CPU del dispositivo mientras se descomprimen las imágenes WebP. Usa Android Studio para convertir tus imágenes a WebP con facilidad.

Inhabilitar la compresión de PNG

Si no conviertes tus imágenes PNG a WebP, puedes de todos modos acelerar tu compilación mediante la inhabilitación de la compresión automática de imágenes cada vez que compilas tu app.

Si usas un complemento de Android para Gradle 3.0.0 o versiones posteriores, la compresión de PNG está inhabilitada de forma predeterminada solo para el tipo de compilación "debug". Si quieres inhabilitar esta optimización para otros tipos de compilación, agrega lo siguiente a tu archivo build.gradle:

Groovy

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

Debido a que los tipos de compilación o las variantes de productos no definen esta propiedad, deberás fijarla manualmente en true cuando compiles la versión de actualización de tu app.

Cómo experimentar con el recolector de elementos no utilizados paralelo de JVM

El rendimiento de la compilación puede mejorarse configurando la recolección de elementos no utilizados de JVM óptima que usa Gradle. Mientras que JDK 8 está configurado para usar el recolector de elementos no utilizados paralelo de forma predeterminada, JDK 9 y las versiones posteriores están configurados para usar el recolector de elementos no utilizados G1.

Para mejorar potencialmente el rendimiento de compilación, te recomendamos que pruebes las compilaciones de Gradle con el recolector de elementos no utilizados paralelo. En gradle.properties, configura lo siguiente:

org.gradle.jvmargs=-XX:+UseParallelGC

Si ya hay otras opciones configuradas en este campo, agrega una opción nueva:

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

A fin de medir la velocidad de compilación con diferentes configuraciones, consulta Cómo generar un perfil para tu compilación.

Cómo aumentar el tamaño del montón de JVM

Si observas compilaciones lentas y, en particular, la recolección de elementos no utilizados lleva más del 15% del tiempo de compilación en los resultados de Build Analyzer, deberías aumentar el tamaño del montón de la máquina virtual de Java (JVM). En el archivo gradle.properties, establece el límite en 4, 6 u 8 gigabytes, como se muestra en el siguiente ejemplo:

org.gradle.jvmargs=-Xmx6g

Luego, realiza pruebas de mejora de la velocidad de compilación. La forma más sencilla de determinar el tamaño óptimo del montón es aumentar el límite en una pequeña cantidad y, luego, realizar pruebas para obtener una mejora suficiente en la velocidad de compilación.

Si también usas el recolector de elementos no utilizados paralelo de JVM, toda la línea debería verse de la siguiente manera:

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

Para analizar los errores de memoria de JVM, activa la marca HeapDumpOnOutOfMemoryError. Como consecuencia, la JVM generará un volcado de montón cuando se quede sin memoria.

Cómo usar clases R no transitivas

Usa clases R no transitivas a fin de obtener compilaciones más rápidas para apps con varios módulos. Esto ayuda a evitar la duplicación de recursos, ya que garantiza que la clase R de cada módulo solo contenga referencias a sus propios recursos, sin extraer referencias de sus dependencias. De esta manera, se generan compilaciones más rápidas y los beneficios correspondientes de evitar la compilación. Este es el comportamiento predeterminado en el complemento de Android para Gradle 8.0.0 y versiones posteriores.

A partir de Android Studio Bumblebee, las clases R no transitivas se activan de forma predeterminada para proyectos nuevos. En el caso de los proyectos creados con versiones anteriores de Android Studio, actualízalos para usar clases R no transitivas en Refactor > Migrate to Non-Transitive R Classes.

Para obtener más información sobre los recursos de la app y la clase R, consulta la Descripción general de los recursos de la app.

Cómo usar clases R no constantes

Usa campos de clase R no constantes en apps y pruebas para mejorar la incrementalidad de la compilación de Java y permitir una reducción más precisa de los recursos. Los campos de la clase R no siempre son constantes para las bibliotecas, ya que los recursos se numeran cuando se empaqueta el APK para la app o la prueba que depende de esa biblioteca. Este es el comportamiento predeterminado en el complemento de Android para Gradle 8.0.0 y versiones posteriores.

Cómo inhabilitar la marca Jetifier

Dado que la mayoría de los proyectos usan bibliotecas de AndroidX directamente, puedes quitar la marca Jetifier para obtener un mejor rendimiento de la compilación. Para quitar la marca Jetifier, configura android.enableJetifier=false en tu archivo gradle.properties.

Build Analyzer puede realizar una verificación para ver si la marca se puede quitar de manera segura a fin de permitir que tu proyecto tenga un mejor rendimiento de compilación y migre fuera de las bibliotecas de compatibilidad de Android desactualizadas. Para obtener más información sobre Build Analyzer, consulta Cómo solucionar problemas de rendimiento de compilación.

Cómo usar la caché de configuración

La caché de configuración permite que Gradle registre información sobre el gráfico de tareas de compilación y la reutilice en compilaciones posteriores, por lo que Gradle no tiene que volver a configurar toda la compilación.

Para habilitar la caché de configuración, sigue estos pasos:

  1. Verifica que todos los complementos del proyecto sean compatibles.

    Usa Build Analyzer para verificar si tu proyecto es compatible con la caché de configuración. Build Analyzer ejecuta una secuencia de compilaciones de prueba a fin de determinar si la función se puede activar para el proyecto. Consulta el error 13490 para obtener una lista de los complementos compatibles.

  2. Agrega el siguiente código al archivo gradle.properties:

      org.gradle.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.configuration-cache.problems=warn

Cuando la caché de configuración está habilitada, la primera vez que ejecutas tu proyecto, el resultado de la compilación dice Calculating task graph as no configuration cache is available for tasks. Durante las ejecuciones posteriores, el resultado de la compilación indicará Reusing configuration cache.

Para obtener más información sobre la caché de configuración, consulta la entrada de blog Configuration caching deep dive (disponible en inglés) y la documentación de Gradle sobre la caché de configuración.

Problemas de caché de configuración introducidos en Gradle 8.1 y el complemento de Android para Gradle 8.1

La caché de configuración se estabilizó en Gradle 8.1 e introdujo el seguimiento de la API de archivos. Gradle graba las llamadas como File.exists(), File.isDirectory() y File.list() para realizar un seguimiento de los archivos de entrada de configuración.

El complemento de Android para Gradle (AGP) 8.1 usa estas APIs de File para algunos archivos que Gradle no debe considerar como entradas de caché. Esto activa la invalidación de caché adicional cuando se usa con Gradle 8.1 y versiones posteriores, lo que ralentiza el rendimiento de la compilación. Las siguientes opciones se tratan como entradas de caché en AGP 8.1:

Entrada Herramienta de seguimiento de errores Fijo en
$GRADLE_USER_HOME/android/FakeDependency.jar Error #289232054 AGP 8.2
salida de CMake Error #287676077 AGP 8.2
$GRADLE_USER_HOME/.android/analytics.settings Error #278767328 AGP 8.3

Si usas estas APIs o un complemento que usa estas APIs, es posible que experimentes una regresión en el tiempo de compilación, ya que parte de la lógica de compilación que usa estas APIs puede activar una invalidación de caché adicional. Consulta Mejoras en el seguimiento de entradas de configuración de compilación para acceder a un análisis de estos patrones y a corregir la lógica de compilación o inhabilitar temporalmente el seguimiento de la API de archivos.