Optimizar tu velocidad de compilación

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

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

  1. Optimizar 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.

Al desarrollar tu app, debes implementar un dispositivo con Android 7.0 (nivel de API 24) o una versión posterior, siempre que sea posible. En las últimas versiones de la plataforma Android se implementan mejores mecanismos para introducir actualizaciones en tu app, como el tiempo de ejecución de Android (ART) y la compatibilidad nativa con varios archivos DEX.

Nota: Después de tu primera compilación limpia, probablemente 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.

Mantener tus herramientas actualizadas

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

Crear una variante de compilación para desarrollo

Muchas de las configuraciones que necesitas al preparar tu app para el lanzamiento no son necesarias mientras la desarrollas. Al habilitar procesos de compilación innecesarios, las compilaciones incrementales y limpias se vuelven más lentas, por lo que debes configurar una variante de compilación que conserve solo las configuraciones de compilación que necesitas para el desarrollo de tu app. En el siguiente ejemplo, se crean un tipo “dev” y un tipo “prod” (para las configuraciones de tu versión de lanzamiento):

android {
  ...
  defaultConfig {...}
  buildTypes {...}
  productFlavors {
    // When building a variant that uses this flavor, the following configurations
    // override those in the defaultConfig block.
    dev {
      // To avoid using legacy multidex when building from the command line,
      // set minSdkVersion to 21 or higher. When using Android Studio 2.3 or higher,
      // the build automatically avoids legacy multidex when deploying to a device running
      // API level 21 or higher—regardless of what you set as your minSdkVersion.
      minSdkVersion 21
      versionNameSuffix "-dev"
      applicationIdSuffix '.dev'
    }

    prod {
      // If you've configured the defaultConfig block for the release version of
      // your app, you can leave this block empty and Gradle uses configurations in
      // the defaultConfig block instead. You still need to create this flavor.
      // Otherwise, all variants use the "dev" flavor configurations.
    }
  }
}

Si en la configuración de tu compilación ya se usan tipos de productos para crear diferentes versiones de tu app, puedes combinar las configuraciones “dev” y “prod” con esos tipos usando dimensiones de tipos. Por ejemplo, si ya configuraste tipos “demo” y “full”, puedes usar el siguiente ejemplo de configuración para crear tipos combinados, como “devDemo” y “prodFull”:

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Specifies the flavor dimensions you want to use. The order in which you
  // list each dimension determines its priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.

  flavorDimensions "stage", "mode"

  productFlavors {
    dev {
      dimension "stage"
      minSdkVersion 21
      versionNameSuffix "-dev"
      applicationIdSuffix '.dev'
      ...
    }

    prod {
      dimension "stage"
      ...
    }

    demo {
      dimension "mode"
      ...
    }

    full {
      dimension "mode"
      ...
    }
  }
}

Evitar compilar recursos innecesarios

Evita compilar y empaquetar recursos que no pruebes (como localizaciones de idioma adicionales y recursos de densidad de pantalla). También puedes hacerlo especificando únicamente un recurso de idioma y una densidad de pantalla para el tipo “dev”, como se muestra en el siguiente ejemplo:

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

Inhabilitar Crashlytics para tus compilaciones de depuración

Si no necesitas ejecutar un informe de Crashlytics, acelera tus compilaciones de depuración inhabilitando el complemento de la siguiente manera:

android {
  ...
  buildTypes {
    debug {
      ext.enableCrashlytics = false
    }
}

También debes inhabilitar el kit de Crashlytics en el tiempo de ejecución para las compilaciones de depuración cambiando la manera en la que inicializas la compatibilidad con Fabric en tu app, como se muestra a continuación:

// Initializes Fabric for builds that don't use the debug build type.
Crashlytics crashlyticsKit = new Crashlytics.Builder()
    .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
    .build();

Fabric.with(this, crashlyticsKit);

Si deseas usar Crashlytics con tus compilaciones de depuración, puedes de todos modos acelerar las compilaciones incrementales evitando que Crashlytics actualice recursos de la app con su ID de compilación único durante cada compilación. Para evitar que Crashlytics actualice constantemente su ID de compilación, agrega lo siguiente a tu archivo build.gradle:

android {
  ...
  buildTypes {
    debug {
      ext.alwaysUpdateBuildId = false
    }
}

Para obtener más información sobre la optimización de tus compilaciones mientras usas Crashlytics, lee la documentación oficial.

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

Usa siempre valores estáticos o no modificables para las propiedades que se incluyen en el archivo de manifest o los archivos de recursos para tu tipo de compilación de depuración. Si los valores de tu archivo de manifest o los recursos de tu app deben actualizarse con cada compilación, Instant Run no podrá cambiar el código; deberá compilar e instalar un APK nuevo.

Por ejemplo, para usar 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 manifest se requiere una compilación del APK completa cada vez que desees 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 archivo build.gradle a continuación.

int MILLIS_IN_MINUTE = 1000 * 60
int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

android {
    ...
    defaultConfig {
        // Making either of these two values dynamic in the defaultConfig will
        // require a full APK build and reinstallation because the AndroidManifest.xml
        // must be updated (which is not supported by Instant Run).
        versionCode 1
        versionName "1.0"
        ...
    }

    // The defaultConfig values above are fixed, so your incremental builds don't
    // need to rebuild the manifest (and therefore the whole APK, slowing build times).
    // But for release builds, it's okay. So the following script iterates through
    // all the known variants, finds those that are "release" build types, and
    // changes those properties to something dynamic.
    applicationVariants.all { variant ->
        if (variant.buildType.name == "release") {
            variant.mergedFlavor.versionCode = minutesSinceEpoch;
            variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
        }
    }
}

Usar versiones de dependencias estáticas

Cuando declares dependencias en tus archivos build.gradle, debes evitar usar números de versión con un signo más al final, como 'com.android.tools.build:gradle:2.+'. El uso de números de versión dinámicos puede generar actualizaciones de versión inesperadas, dificultar la resolución de diferencias de versión y ralentizar las compilaciones debido a que Gradle busca actualizaciones. Como alternativa, debes usar números de versión estáticos o no modificables.

Habilitar el modo sin conexión

Si tu conexión de red es lenta, los tiempos de compilación pueden verse afectados cuando Gradle intente usar recursos de la red para resolver dependencias. Puedes indicar a Gradle que evite usar recursos de red y use solo los artefactos almacenados en caché a nivel local.

Para usar Gradle sin conexión cuando realices compilaciones con Android Studio, haz lo siguiente:

  1. Abre la ventana Preferences haciendo clic en File > Settings (en Mac, Android Studio > Preferences).
  2. En el subpanel izquierdo, haz clic en Build, Execution, Deployment > Gradle.
  3. Selecciona la casilla de verificación Offline work.
  4. Haz clic en Apply o en OK.

Si realizas la compilación desde la línea de comandos, pasa la opción --offline.

Habilitar la configuración a pedido

Para que Gradle determine con exactitud cómo compilar tu app, el sistema de compilación configura todos los módulos de tu proyecto y las dependencias de estos antes de cada compilación (aunque compiles y pruebes un solo módulo). Esto ralentiza el proceso de compilación en el caso de los proyectos grandes con varios módulos. Para indicar a Gradle que configure solo los módulos que desees compilar, habilita la configuración a pedido de la siguiente manera:

  1. Abre la ventana Preferences haciendo clic en File > Settings (en Mac, Android Studio > Preferences).
  2. En el subpanel izquierdo, haz clic en Build, Execution, Deployment > Compiler.
  3. Selecciona la casilla de verificación Configure on demand.
  4. Haz clic en Apply o en OK.

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 configuración a pedido y la ejecución de objetos en paralelo (cuando habilitas esas funciones).

Crear tareas para lógica de compilación personalizada

Una vez que crees un perfil para la compilación, si en este se muestra que una fracción relativamente larga del tiempo de compilación se dedica a la etapa de “configuración de proyectos”, revisa tus secuencias de comandos de build.gradle y busca código que puedas incluir en una tarea de Gradle personalizada. Al trasladar parte de la lógica de compilación a una tarea, que se ejecutará únicamente cuando sea necesario, los resultados se pueden almacenar en caché para compilaciones futuras y la lógica de compilación reunirá las condiciones para ejecutarse en paralelo (si habilitas la ejecución de objetos en paralelo). Para obtener más información, 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 los incluirá automáticamente en la classpath para todos los archivos build.gradle de tu proyecto.

Configurar dexOptions y habilitar la conversión previa a DEX en la biblioteca

El complemento de Android proporciona el bloque dexOptions para que puedas configurar las siguientes propiedades de compilación DEX, que pueden mejorar las velocidades de compilación:

  • preDexLibraries: declara si las dependencias de la biblioteca se deben convertir a DEX para que tus compilaciones incrementales sean más rápidas. Debido a que esta función puede hacer ralentizar tus compilaciones limpias, te recomendamos inhabilitar esta función para el servidor de integración continua.
  • maxProcessCount: establece la cantidad máxima de subprocesos que se pueden usar mientras se ejecuta el proceso de conversión a DEX. El valor predeterminado es 4.
  • javaMaxHeapSize: establece el tamaño máximo del montón para el compilador de DEX. No obstante, en lugar de configurar esta propiedad, debes aumentar el tamaño del montón de Gradle (que se comparte con el compilador de DEX cuando se habilita el proceso de conversión a DEX).

Por ejemplo:

android {
  ...
  dexOptions {
    preDexLibraries true
    maxProcessCount 8
    // Instead of setting the heap size for the DEX process, increase Gradle's
    // heap size to enable dex-in-process. To learm more, read the next section.
    // javaMaxHeapSize "2048m"
  }
}

Debes experimentar con estas configuraciones aumentando sus valores y observar los efectos creando perfiles para tu compilación. Es posible que observes un impacto negativo en el rendimiento si asignas demasiados recursos al proceso.

Nota: Si el daemon de Gradle ya se encuentra en ejecución, debes detener el proceso antes de inicializarlo con un nueva configuración. Puedes finalizar el daemon de Gradle seleccionando View > Tool Windows > Terminal e introduciendo el siguiente comando: gradlew --stop.

Aumentar el tamaño del montón de Gradle y habilitar el proceso de conversión a DEX

El proceso de conversión a DEX ejecuta el compilador de DEX dentro del proceso de compilación en lugar de hacerlo en un proceso VM independiente. Esto hace que las compilaciones incrementales y limpias sean más rápidas. De forma predeterminada, los proyectos nuevos que se crean con Android Studio 2.1 y versiones posteriores asignan al proceso de compilación una cantidad de memoria suficiente para habilitar esta función. Si no creaste tu proyecto usando Android Studio 2.1 o una versión posterior, debes fijar el tamaño máximo del montón del daemon de Gradle en al menos 1536 MB. En el siguiente ejemplo el tamaño del montón de Gradle se fija en 2048 MB en el archivo gradle.properties de tu proyecto:

org.gradle.jvmargs = -Xmx2048m

Algunos proyectos más grandes pueden beneficiarse con un tamaño de montón de Gradle aún mayor. No obstante, si usas una máquina con poca memoria, probablemente debas configurar el IDE para que use menos memoria. Para obtener más información sobre cómo el cambio de la cantidad de recursos que asignas al IDE y a Gradle afecta el rendimiento de tu compilación, consulta la sección generar perfiles para tu compilación.

Nota: Si el daemon de Gradle ya se encuentra en ejecución, debes detener el proceso antes de inicializarlo con un nueva configuración. Puedes finalizar el daemon de Gradle seleccionando View > Tool Windows > Terminal e introduciendo el siguiente comando: gradlew --stop.

Si defines un valor para android.dexOptions.javaMaxHeapSize en el archivo build.gradle de tu módulo, que controla el tamaño del montón para el compilador de DEX, debes fijar el tamaño del montón de Gradle de modo que supere en 512 MB el valor que configuraste para la propiedad javaMaxHeapSize y que su tamaño sea de al menos 1536 MB. Por ejemplo, si fijas javaMaxHeapSize en 1280 MB, debes establecer org.gradle.jvmargs en al menos 1792 MB (1280 + 512), si bien resultará mejor un tamaño de montón aún mayor. No necesitas especificar un valor para javaMaxHeapSize a fin de habilitar el proceso de conversión a DEX. Si excluyes javaMaxHeapSize de la configuración de tu compilación, asegúrate de fijar el tamaño del montón de Gradle en 1536 o más MB.

Convertir imágenes a WebP

WebP es un formato de archivo de imagen que proporciona compresión con pérdida de información (como JPEG) y transparencia (como PNG), pero puede ofrecer mejor compresión que estos otros dos formatos. 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 descomprime imágenes WebP. Con Android Studio, puedes convertir tus imágenes a WebP fácilmente.

Inhabilitar la compresión de PNG

Si no puedes (o no deseas) convertir tus imágenes PNG a WebP, puedes de todos modos acelerar tu compilación inhabilitando la compresión automática de imágenes cada vez que compilas tu app. Para inhabilitar esta optimización, agrega lo siguiente a tu archivo build.gradle:

android {
  ...
  aaptOptions {
    cruncherEnabled false
  }
}

Debido a que los tipos de compilación o de productos no definen esta propiedad, debes fijarla manualmente en true cuando compiles la versión de lanzamiento de tu app.

Habilitar Instant Run

Instant Run reduce notablemente el tiempo de actualización de tu app al aplicar determinados cambios en el código y los recursos sin compilar un APK nuevo y, en algunos casos, sin reiniciar la actividad actual. Usa Instant Run haciendo clic en Apply Changes después de realizar los cambios en tu app. Con esto, quedará habilitado de forma predeterminada cuando realices los siguiente:

  • Compila tu app usando una variante de compilación de depuración.
  • Usa la versión 2.3.0 o una posterior del complemento de Android para Gradle.
  • Fija minSdkVersion en el valor 15, o en uno superior, en el archivo de nivel de módulo build.gradle de tu app.
  • Implementa tu app en un dispositivo con Android 5.0 (nivel de API 21) y versiones posteriores haciendo clic en Run .

Si no ves el botón Apply Changes en la barra de herramientas después de cumplir con estos requisitos, asegúrate de que Instant Run no esté inhabilitado en la configuración de IDE; para esto, aplica los pasos siguientes:

  1. Abre el diálogo Settings o Preferences.
  2. Dirígete a Build, Execution, Deployment > Instant Run.
  3. Asegúrate de que Enable Instant Run esté seleccionado.

Habilitar el caché de compilación

El caché de depuración guarda determinada información de salida que genera el complemento de Android para Gradle al compilar tu proyecto (como AAR sin empaquetar y dependencias remotas previamente convertidas a archivos Dex). Tus compilaciones limpias son mucho más rápidas mientras se usa el caché, ya que el sistema de compilación puede simplemente reutilizar esos archivos almacenados en caché en compilaciones posteriores en lugar de volver a crearlos.

Los proyectos nuevos que usan el complemento de Android 2.3.0 y versiones posteriores habilitan el caché de compilación de forma predeterminada (a menos que inhabilites el caché de compilación de forma explícita). Para obtener más información, lee Acelerar compilaciones limpias con el caché de compilación.

Inhabilitar procesadores de anotaciones

La compilación incremental de Java se inhabilita cuando se usan procesadores de anotaciones. Si es posible, evita usar procesadores de anotaciones para beneficiarte con la compilación solo de las clases que modificas entre compilaciones.

Generar un perfil para tu compilación

Los proyectos más grandes, o aquellos que implementan una gran cantidad de lógica de compilación predeterminada, pueden exigir que indagues más en el proceso de compilación para detectar cuellos de botella. Puedes hacerlo generando perfiles sobre el tiempo que Gradle tarda en ejecutar cada etapa del ciclo de vida de la compilación y cada tarea de compilación. Por ejemplo, si tu perfil de compilación muestra que Gradle tarda mucho tiempo en configurar tu proyecto, esto podría sugerir que necesitas mover lógica de compilación personalizada fuera de la etapa de configuración. Además, si la tarea mergeDevDebugResources consume una gran cantidad de tiempo de compilación, esto podría indicar que necesitas convertir tus imágenes a WebP o inhabilitar la compresión de PNG.

Usar la generación de perfiles para mejorar la velocidad de la compilación generalmente implica ejecutar tu compilación con la generación de perfiles habilitada, realizar algunos ajustes en la configuración de la compilación y generar más perfiles para observar los resultados de tus cambios.

Para generar y ver un perfil de compilación, realiza los siguientes pasos:

  1. Con tu proyecto abierto en Android Studio, selecciona View > Tool Windows > Terminal para abrir una línea de comandos en la raíz de tu proyecto.
  2. Realiza una compilación limpia ingresando el siguiente comando. A medida que generes el perfil de tu compilación, debes realizar una compilación limpia entre cada compilación de la que generes perfiles, ya que Gradle omite tareas cuando las entradas de estas (como el código fuente) no cambian. De este modo, una segunda compilación sin cambios en las entradas siempre se ejecuta más rápido porque las tareas no se vuelven a ejecutar. Por lo tanto, ejecutar la tarea clean entre tus compilaciones garantiza que generes un perfil del proceso de compilación completo.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Ejecuta una compilación de depuración de uno de tus tipos de productos, como el tipo “dev”, con los siguientes indicadores:
    gradlew --profile --recompile-scripts --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: habilita la generación de perfiles.
    • --recompile-scripts: fuerza la repetición de la compilación de las secuencias de comandos mientras se omite el almacenamiento en caché.
    • --offline: inhabilita la obtención de dependencias en línea por parte de Gradle. Esto garantiza que las demoras provocadas por Gradle cuando intenta actualizar tus dependencias no interfieran con los datos de la generación de perfiles. Para poder asegurarte de que Gradle haya descargado y almacenado en caché tus dependencias, es necesario que hayas compilado tu proyecto una vez.
    • --rerun-tasks: hace que Gradle, de manera forzosa, vuelva a ejecutar todas las tareas e ignore las optimizaciones de tareas.
  4. Una vez que finalice la compilación, usa la ventana Project para acceder al directorio project-root/build/reports/profile/ (como se muestra en la figura 1).

    Figura 1: Vista del proyecto que indica la ubicación de los informes del perfil.

  5. Haz clic con el botón secundario en el archivo profile-timestamp.html y selecciona Open in Browser > Default. El informe debe ser similar al que se muestra en la figura 2. Puedes inspeccionar cada pestaña de este para obtener información sobre tu compilación, como la pestaña Task Execution, en la que se muestra el tiempo que Gradle tardó en ejecutar cada tarea de compilación.

    Figura 2: Visualización de un informe en un navegador.

  6. Opcional: antes de realizar cambios en la configuración de tu proyecto o compilación, repite el comando del paso 3, pero omite el indicador --rerun-tasks. Debido a que Gradle intenta ahorrar tiempo al no volver a ejecutar tareas cuyas entradas no han cambiado (se indican como UP-TO-DATE en la pestaña Task Execution del informe, como se muestra en la figura 3), puedes identificar las tareas en ejecución que no deberían estar activas. Por ejemplo, si :app:processDevUniversalDebugManifest no se marca como UP-TO-DATE, esto puede sugerir que la configuración de tu compilación actualiza el manifest de forma dinámica con cada compilación. No obstante, algunas tareas deben ejecutarse durante cada compilación, como :app:checkDevDebugManifest.

    Figura 3: Visualización de los resultados de la ejecución de tareas.

Ahora que tienes un informe de perfil de compilación, puedes comenzar a buscar oportunidades de optimización al inspeccionar la información de cada pestaña del informe. Para algunas configuraciones de la compilación se requiere experimentación, ya que los beneficios pueden diferir entre proyectos y estaciones de trabajo. Por ejemplo, usando ProGuard para quitar el código que no se utilice y reducir el tamaño del APK se pueden obtener beneficios en proyectos que tengan una base de código grande. No obstante, es posible lograr más beneficios para proyectos más pequeños mediante la inhabilitación de ProGuard. Además, aumentar el tamaño del montón de Gradle puede afectar negativamente el rendimiento de equipos con poca memoria.

Después de realizar un cambio en la configuración de tu compilación, observa los resultados de este repitiendo los pasos anteriores y generando un nuevo perfil de compilación. Por ejemplo, en la figura 4 se muestra un informe para el mismo ejemplo de app después de aplicar algunas de las optimizaciones básicas descritas en esta página.

Figura 4: Visualización de un informe nuevo después de optimizar la velocidad de compilación.

Sugerencia: Si buscas una herramienta de generación de perfiles más eficaz, considera usar el generador de perfiles de código abierto de Gradle.