Cómo 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. 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. Genera 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 (nivel de API 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, 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).

Cómo 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, 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:

Cómo crear una variante de compilación para desarrollo

Muchas de las configuraciones que necesitas cuando preparas tu app para el lanzamiento no son necesarias durante el desarrollo. Cuando habilitas 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 necesites para el desarrollo de tu app. En el siguiente ejemplo, se crean una variante "dev" y una variante "prod" (para las configuraciones de tu versión de actualización):

Groovy

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.
        }
    }
}

Kotlin

android {
    ...
    defaultConfig {...}
    buildTypes {...}
    productFlavors {
        // When building a variant that uses this flavor, the following configurations
        // override those in the defaultConfig block.
        create("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"
        }

        create("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 variantes de productos para crear diferentes versiones de tu app, puedes combinar las configuraciones "dev" y "prod" con esos tipos usando dimensiones de variantes. Por ejemplo, si ya configuraste las variantes "demo" y "full", puedes usar el siguiente ejemplo de configuración para crear variantes combinadas, como "devDemo" y "prodFull":

Groovy

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"
            ...
        }
    }
}

Kotlin

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 {
        create("dev") {
            dimension = "stage"
            minSdkVersion(21)
            versionNameSuffix = "-dev"
            applicationIdSuffix = ".dev"
            ...
        }

        create("prod") {
            dimension = "stage"
            ...
        }

        create("demo") {
            dimension = "mode"
            ...
        }

        create("full") {
            dimension = "mode"
            ...
        }
    }
}

Sincronización de proyectos con una sola variante

Sincronizar tu proyecto con la configuración de compilación es un paso importante para que Android Studio pueda comprender la manera en que está estructurado tu proyecto. Sin embargo, en el caso de proyectos grandes, el proceso puede consumir mucho tiempo. Si tu proyecto usa múltiples variantes de compilación, Android Studio optimiza las sincronizaciones del proyecto limitándolas solo a la variante que hayas seleccionado.

Esta optimización está habilitada de forma predeterminada en todos los proyectos y ya no se puede configurar en Android Studio 4.2 y versiones posteriores.

Para habilitar esta optimización manualmente, debes usar Android Studio 3.3 o una versión posterior con el complemento de Android para Gradle 3.3.0 o una versión posterior. Haz clic en File > Settings > Experimental > Gradle (Android Studio > Preferences > Experimental > Gradle en Mac) y selecciona la casilla de verificación Only sync the active variant.

Nota: Esta optimización es completamente compatible con proyectos que incluyen Java y lenguajes C++, y ofrece compatibilidad limitada con Kotlin. Cuando se habilita la optimización para proyectos con contenido de Kotlin, la sincronización de Gradle vuelve a usar variantes completas de forma interna.

Cómo 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). También puedes hacerlo especificando únicamente 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.
            resConfigs "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

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

Cómo 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:

Groovy

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

Kotlin

android {
    ...
    buildTypes {
        getByName("debug") {
            extra["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:

Kotlin

// Initializes Fabric for builds that don't use the debug build type.
Crashlytics.Builder()
        .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
        .build()
        .also { crashlyticsKit ->
            Fabric.with(this, crashlyticsKit)
        }

Java

// 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);

Cómo inhabilitar la generación automática de ID de compilación

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. Como este ID de compilación se almacena en un archivo de recurso al que hace referencia el manifiesto, si inhabilitas la generación automática de ID de compilación también podrás usar Apply Changes junto con Crashlytics para las compilaciones de depuración.

Para evitar que Crashlytics actualice automáticamente su ID de compilación, agrega lo siguiente a tu archivo build.gradle:

Groovy

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

Kotlin

android {
    ...
    buildTypes {
        getByName("debug") {
            extra["alwaysUpdateBuildId"] = false
        }
    }
}

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

Cómo 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 manifiesto o los archivos de recursos para tu tipo de compilación de depuración.

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 manifiesto, se requiere una compilación del APK 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. Para ver un ejemplo, consulta a continuación el archivo build.gradle.

Groovy

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.
        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;
        }
    }
}

Kotlin

val MILLIS_IN_MINUTE = 1000 * 60
val 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.
        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
        }
    }
}

Cómo usar versiones de dependencias estáticas

Cuando declares dependencias en tus archivos build.gradle, deberás 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 hard-coded.

Cómo 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).

Cómo 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. Si se traslada 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 con el objetivo de 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 ruta de clase para todos los archivos build.gradle de tu proyecto.

Convierte imágenes a WebP

WebP es un formato de archivo de imagen que proporciona compresión con pérdida (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 se descomprimen las imágenes WebP. Con Android Studio, puedes convertir tus imágenes a WebP con facilidad.

Cómo inhabilitar la compresión de PNG

Si no puedes (o no deseas) convertir tus imágenes PNG a WebP, puedes 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 3.0.0 o posterior, 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
        }
    }

// If you're using an older version of the plugin, use the
// following:
//  aaptOptions {
//      cruncherEnabled false
//  }
}

Kotlin

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

// If you're using an older version of the plugin, use the
// following:
//  aaptOptions {
//      cruncherEnabled = 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 usar procesadores de anotaciones graduales

La versión 3.3.0 del complemento de Gradle para Android y las versiones posteriores mejoran la compatibilidad con el procesamiento de anotaciones incrementales. Por lo tanto, a fin de mejorar la velocidad de las compilaciones graduales, debes actualizar el complemento de Gradle para Android y utilizar solo procesadores de anotación graduales siempre que sea posible.

Nota: Esta función es compatible con Gradle 4.10.1 y sus versiones posteriores, excepto Gradle 5.1 (consulta el problema de Gradle número 8194).

Para comenzar, consulta la siguiente tabla de procesadores de anotaciones populares que admiten el procesamiento de anotaciones gradual. La lista completa está disponible en Estado de compatibilidad en los procesadores de anotaciones populares. Es posible que algunos de los procesadores de anotaciones requieran pasos adicionales para habilitar la optimización, así que asegúrate de leer la documentación de cada uno.

Además, si usas Kotlin en tu app, debes usar kapt 1.3.30 y versiones posteriores si deseas admitir procesadores de anotaciones graduales para tu código de Kotlin. Asegúrate de leer la documentación oficial para saber si necesitas habilitar manualmente este comportamiento.

Ten en cuenta que, si tienes que usar uno o más procesadores de anotaciones que no admiten compilaciones graduales, el procesamiento de anotaciones no será incremental. Sin embargo, aunque el proyecto use kapt, la compilación de Java aún será gradual.

Compatibilidad con procesadores de anotaciones graduales

Nombre del proyectoNombre de clase del procesador de anotacionesCompatible desde…
DataBindingandroid.databinding.annotationprocessor.ProcessDataBindingAGP 3.5
Roomandroidx.room.RoomProcessor2.3.0-alpha02

2.20: Usa la opción de room.incremental.
ButterKnifebutterknife.compiler.ButterKnifeProcessor10.2.0
Glidecom.bumptech.glide.annotation.compiler.GlideAnnotationProcessor4.9.0
Daggerdagger.internal.codegen.ComponentProcessor2.18
Lifecycleandroidx.lifecycle.LifecycleProcessor2.2.0-alpha02
AutoServicecom.google.auto.service.processor.AutoServiceProcessor1.0-rc7
Daggerdagger.android.processor.AndroidProcessor2.18
Realmio.realm.processor.RealmProcessor5.11.0
Lomboklombok.launch.AnnotationProcessorHider$AnnotationProcessor1.16.22
Lomboklombok.launch.AnnotationProcessorHider$ClaimingProcessor1.16.22

Cómo configurar el recolector de elementos no utilizados 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

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