Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Cómo reducir, ofuscar y optimizar tu app

Para que tu app sea lo más pequeña posible, deberías habilitar la reducción en tu compilación de lanzamiento a fin de quitar el código y los recursos que no se usan. Al habilitar esta opción, también te beneficiarás de la ofuscación, que acorta los nombres de las clases y los miembros de tu app, y de la optimización, que aplica estrategias más agresivas a fin de reducir aún más el tamaño de tu app. En esta página, se describe cómo R8 realiza estas tareas en el tiempo de compilación para tu proyecto y cómo puedes personalizarlas.

Al compilar tu proyecto mediante el complemento de Android para Gradle 3.4.0 o una versión posterior, el complemento ya no usa ProGuard a fin de realizar la optimización de código en el tiempo de compilación. En su lugar, el complemento trabaja con el Compilador R8 para administrar las siguientes tareas en el tiempo de compilación:

  • Reducción de código (o eliminación de código obsoleto): Detecta y quita de forma segura de tu app las clases, los campos, los métodos y los atributos que no se usan y sus dependencias de biblioteca (lo que la convierte en una herramienta útil para evitar sobrepasar el límite de referencia de 64 mil). Por ejemplo, si usas solo unas pocas API de una dependencia de biblioteca, la reducción puede identificar y quitar el código de biblioteca que tu app no usa. Para obtener más información, ve a la sección sobre cómo reducir tu código.
  • Reducción de recursos: Quita los recursos que no se usan de tu app empaquetada, incluidos los que no se usan en las dependencias de la biblioteca de tu app. Funciona junto con la reducción de código, de modo que cuando se quite el código que no se usa, los recursos a los que ya no se haga referencia también puedan quitarse de forma segura. Para obtener más información, consulta la sección sobre cómo reducir tus recursos.
  • Ofuscación: Acorta el nombre de las clases y los miembros, lo que genera tamaños de archivo DEX reducidos. Para obtener más información, ve a la sección sobre cómo ofuscar tu código.
  • Optimización: Inspecciona y vuelve a escribir el código para reducir aún más el tamaño de los archivos DEX de tu app. Por ejemplo, si R8 detecta que nunca se toma la rama else {} para una declaración "if/else" determinada, R8 quita el código para esa rama else {}. Para obtener más información, ve a la sección sobre optimización de código.

Al crear la versión de lanzamiento de tu app, de forma predeterminada, R8 realiza automáticamente las tareas en tiempo de compilación descritas anteriormente. Sin embargo, puedes inhabilitar ciertas tareas o personalizar el comportamiento de R8 por medio de los archivos de reglas ProGuard. De hecho, R8 funciona con todos tus archivos de reglas ProGuard existentes, por lo que si quieres actualizar el complemento de Android para Gradle a fin de que use R8, no es necesario que cambies las reglas existentes.

Cómo habilitar la reducción, la ofuscación y la optimización

Cuando usas Android Studio 3.4 o el complemento de Android para Gradle 3.4.0 y versiones posteriores, R8 es el compilador predeterminado que convierte el código de byte Java de tu proyecto al formato DEX que se ejecuta en la plataforma Android. Sin embargo, cuando creas un nuevo proyecto con Android Studio, las opciones de reducción, ofuscación y optimización de código no están habilitadas de manera predeterminada, ya que estas optimizaciones en tiempo de compilación aumentan el tiempo que tarda en compilarse tu proyecto y pueden introducir errores si no determinas de forma personalizada el código que se conservará correctamente.

Por lo tanto, es mejor que habilites estas tareas en el tiempo de compilación cuando compiles la versión final de la app que probarás antes de publicar. Para habilitar la reducción, la ofuscación y la optimización, incluye lo siguiente en tu archivo build.gradle en el nivel del proyecto.

android {
        buildTypes {
            release {
                // Enables code shrinking, obfuscation, and optimization for only
                // your project's release build type.
                minifyEnabled true

                // Enables resource shrinking, which is performed by the
                // Android Gradle plugin.
                shrinkResources true

                // Includes the default ProGuard rules files that are packaged with
                // the Android Gradle plugin. To learn more, go to the section about
                // R8 configuration files.
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro'
            }
        }
        ...
    }
    

Archivos de configuración de R8

R8 usa archivos de reglas ProGuard para modificar su comportamiento predeterminado y comprender mejor la estructura de tu app, como las clases que actúan como puntos de entrada al código de tu app. Si bien puedes modificar algunos de los archivos de reglas, es posible que algunas reglas se generen automáticamente por medio de las herramientas de tiempo de compilación, como AAPT2, o se hereden de las dependencias de la biblioteca de tu app. En la siguiente tabla, se describen las fuentes de los archivos de reglas ProGuard que usa R8.

Fuente Ubicación Descripción
Android Studio <module-dir>/proguard-rules.pro Cuando creas un nuevo módulo con Android Studio, el IDE crea un archivo proguard-rules.pro en el directorio raíz de ese módulo.

De manera predeterminada, este archivo no aplica ninguna regla. Por lo tanto, deberás incluir tus propias reglas ProGuard aquí, como tus reglas de conservación personalizadas.

Complemento Gradle para Android El complemento de Android para Gradle lo genera en el momento de la compilación. El complemento de Android para Gradle genera el archivo proguard-android-optimize.txt, que incluye reglas que son útiles para la mayoría de los proyectos de Android y habilita las anotaciones @Keep*.

De forma predeterminada, al crear un módulo nuevo con Android Studio, el archivo build.gradle en el nivel del módulo incluye este archivo de reglas en la versión por ti.

Nota: El complemento de Android para Gradle incluye archivos de reglas ProGuard predefinidos adicionales, pero te recomendamos que uses proguard-android-optimize.txt.

Dependencias de bibliotecas Bibliotecas AAR: <library-dir>/proguard.txt

Bibliotecas JAR: <library-dir>/META-INF/proguard/

Si una biblioteca AAR se publica con su propio archivo de reglas ProGuard y además incluyes ese AAR como una dependencia de tiempo de compilación, R8 aplicará automáticamente sus reglas al compilar tu proyecto.

El uso de archivos de reglas que se empaquetan con bibliotecas AAR es útil si se requieren ciertas reglas de conservación a fin de que la biblioteca funcione correctamente (es decir, el desarrollador de la biblioteca realizó los pasos para la solución de problemas por ti).

Sin embargo, debes tener en cuenta que, como las reglas ProGuard son aditivas, las reglas que incluyan una dependencia de la biblioteca AAR no se podrán quitar y es posible que afecten la compilación de otras partes de tu app. Por ejemplo, si una biblioteca incluye una regla para inhabilitar las optimizaciones de código, esta inhabilitará las optimizaciones de todo tu proyecto.

Android Asset Package Tool 2 (AAPT2) Luego de compilar tu proyecto con minifyEnabled true: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 genera reglas de conservación basadas en referencias a las clases en el manifiesto de tu app, a los diseños y a otros recursos de app. Por ejemplo, AAPT2 incluye una regla de conservación para cada Actividad que registras como punto de entrada en el manifiesto de tu app.
Archivos de configuración personalizados De manera predeterminada, cuando creas un nuevo módulo con Android Studio, el IDE crea el archivo <module-dir>/proguard-rules.pro para que agregues tus propias reglas. Puedes incluir opciones de configuración adicionales y R8 las aplicará en el momento de la compilación.

Cuando configuras la propiedad minifyEnabled en true, R8 combina reglas de todas las fuentes disponibles mencionadas anteriormente. Es importante que lo recuerdes cuando soluciones problemas con R8 porque otras dependencias en tiempo de compilación (como las dependencias de la biblioteca) podrían introducir cambios en el comportamiento de R8 que desconoces.

Para generar un informe completo sobre todas las reglas que R8 aplica al compilar tu proyecto, incluye lo siguiente en el archivo proguard-rules.pro de tu módulo:

// You can specify any path and filename.
    -printconfiguration ~/tmp/full-r8-config.txt
    

Cómo incluir opciones de configuración adicionales

Cuando creas un nuevo proyecto o módulo con Android Studio, el IDE crea un archivo <module-dir>/proguard-rules.pro para que incluyas tus propias reglas. También puedes incluir reglas adicionales de otros archivos agregándolas a la propiedad proguardFiles en el archivo build.gradle de tu módulo.

Por ejemplo, puedes agregar reglas que son específicas de cada variante de compilación incluyendo otra propiedad proguardFiles en el bloque productFlavor correspondiente. El siguiente archivo de Gradle agregará flavor2-rules.pro al tipo de producto flavor2. A continuación, flavor2 usará las tres reglas ProGuard porque las del bloque release también se aplicarán.

android {
        ...
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile(
                  'proguard-android-optimize.txt'),
                  // List additional ProGuard rules for the given build type here. By default,
                  // Android Studio creates and includes an empty rules file for you (located
                  // at the root directory of each module).
                  'proguard-rules.pro'
            }
        }
        flavorDimensions "version"
        productFlavors {
            flavor1 {
              ...
            }
            flavor2 {
                proguardFile 'flavor2-rules.pro'
            }
        }
    }
    

Cómo reducir tu código

La reducción de código con R8 se habilita de forma predeterminada cuando configuras la propiedad minifyEnabled en true.

La reducción de código (también conocida como eliminación de código muerto) es el proceso de eliminación de código que R8 determina que no es necesario en el tiempo de ejecución. Este proceso puede reducir en gran medida el tamaño de tu app si, por ejemplo, esta incluye muchas dependencias de biblioteca, pero usa solo una pequeña parte de su funcionalidad.

Para reducir el código de tu app, R8 primero determina todos los puntos de entrada al código de tu app en función de la combinación del conjunto de archivos de configuración. Estos puntos de entrada incluyen todas las clases que la plataforma Android puede usar para abrir las actividades o servicios de tu app. R8 inspecciona el código de tu app a partir de cada punto de entrada a fin de compilar un gráfico de todos los métodos, variables de miembro y otras clases a las que tu app podría acceder en el tiempo de ejecución. El código que no está conectado a ese gráfico se considera inaccesible y puedes quitarlo de tu app.

En la figura 1, se muestra una app con una dependencia de biblioteca de tiempo de ejecución. Al inspeccionar el código de la app, R8 determina que es posible acceder a los métodos foo(), faz() y bar() desde el punto de entrada MainActivity.class. Sin embargo, tu app nunca usa la clase OkayApi.class o su método baz() en el tiempo de ejecución, por lo tanto, R8 quitará ese código cuando reduzca tu app.

Figura 1: En el tiempo de compilación, R8 compila un gráfico basado en las reglas de conservación combinadas de tu proyecto para determinar el código inaccesible.

R8 determina los puntos de entrada por medio de reglas -keep en los archivos de configuración de R8 del proyecto. Es decir, las reglas de conservación especifican las clases que R8 no debe descartar al reducir tu app, y R8 las considera como posibles puntos de entrada a tu app. El complemento de Android para Gradle y AAPT2 generan automáticamente las reglas de conservación que requieren la mayoría de los proyectos de apps, como las actividades, las vistas y los servicios de tu app. Sin embargo, si necesitas personalizar este comportamiento predeterminado con reglas de conservación adicionales, lee la sección sobre cómo determinar de forma personalizada el código que se conservará.

En cambio, si solo quieres reducir el tamaño de los recursos de tu app, ve a la sección sobre cómo reducir tus recursos.

Cómo determinar de forma personalizada el código que se conservará

En la mayoría de las situaciones, el archivo predeterminado de reglas ProGuard (proguard-android- optimize.txt) es suficiente para que R8 quite únicamente el código que no se usa. Sin embargo, R8 tiene dificultades para analizar algunas situaciones y es posible que quite código que tu app realmente necesita. Los siguientes son ejemplos de situaciones en las cuales este podría quitar código de forma incorrecta:

  • Cuando tu app llama a un método desde la Interfaz nativa Java (JNI)
  • Cuando tu app busca código en el tiempo de ejecución (como con la reflexión)

Al probar tu app, deberías ver los errores que se produjeron por la eliminación incorrecta de código, pero también puedes generar un informe sobre el código quitado a fin de inspeccionar el código que se quitó.

Para corregir los errores y forzar a R8 a que conserve cierto código, agrega una línea -keep en el archivo de reglas ProGuard. Por ejemplo:

-keep public class MyClass
    

De manera alternativa, puedes agregar la anotación @Keep al código que desees conservar. Si se agrega @Keep en una clase, se conserva toda la clase como está. Si se agrega a un método o campo, se mantendrán intactos tanto el campo o método (y su nombre) como el nombre de la clase. Ten en cuenta que esta anotación solo está disponible cuando se usa la Biblioteca de anotaciones de AndroidX y además incluyes el archivo de reglas ProGuard que se empaqueta con el complemento de Android para Gradle, como se describe en la sección sobre cómo habilitar la reducción.

Existen muchas consideraciones que debes tener en cuenta al usar la opción -keep. Para obtener más información sobre cómo personalizar tu archivo de reglas, lee el Manual de ProGuard. En la sección Solución de problemas, se describen otros problemas comunes que podrías encontrar al reducir tu código.

Cómo reducir tus recursos

La reducción de recursos funciona únicamente junto con la reducción de código. Una vez que el reductor de código quita todo el código que no se usa, el reductor de recursos puede identificar los recursos que la app todavía usa, especialmente cuando agregas bibliotecas de código que incluyen recursos. En este caso, deberás quitar el código de biblioteca que no se usa de modo que se deje de hacer referencia a los recursos de biblioteca y el reductor de recursos pueda quitarlos.

Para habilitar la reducción de recursos, configura la propiedad shrinkResources en true en tu archivo build.gradle (junto con minifyEnabled para reducir código). Por ejemplo:

android {
        ...
        buildTypes {
            release {
                shrinkResources true
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                        'proguard-rules.pro'
            }
        }
    }
    

Si todavía no compilaste tu app con minifyEnabled para reducir código, entonces intenta hacerlo antes de habilitar shrinkResources, ya que es posible que debas editar tu archivo proguard-rules.pro a fin de conservar las clases o los métodos que se creen o invoquen de manera dinámica antes de que empieces a quitar recursos.

Cómo determinar de forma personalizada los recursos que se conservarán

Si hay recursos específicos que quieres conservar o descartar, crea un archivo XML en tu proyecto con una etiqueta <resources> y especifica cada recurso que desees conservar en el atributo tools:keep y cada recurso que quieras descartar en el atributo tools:discard. Ambos atributos aceptan una lista de nombres de recursos separados por comas. Puedes usar el carácter de asterisco como comodín.

Por ejemplo:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
        tools:discard="@layout/unused2" />
    

Guarda este archivo en los recursos de tu proyecto; por ejemplo, en res/raw/keep.xml. Durante la compilación, este archivo no se empaquetará en tu APK.

Especificar los recursos que deben descartarse podría parecer absurdo ya que en su lugar podrías borrarlos, pero podría resultar útil cuando usas variantes de compilación. Por ejemplo, podrías ubicar todos tus recursos en el directorio común del proyecto y crear un archivo keep.xml diferente para cada variante de compilación cuando sabes que es posible que un recurso determinado se use en el código (y, por lo tanto, el reductor no lo quitará), pero que no se usará para la variante de compilación determinada. También podría ocurrir que las herramientas de compilación hayan identificado incorrectamente un recurso como necesario, lo que podría suceder porque el compilador agrega los ID de recursos intercalados y, por lo tanto, es posible que el analizador de recursos no pueda diferenciar un recurso de un valor de número entero al que se hace referencia de manera genuina en el código que casualmente tiene el mismo valor.

Cómo habilitar comprobaciones de referencia estrictas

Por lo general, el reductor de recursos puede determinar de manera precisa si un recurso se usa o no. Sin embargo, si tu código llama a Resources.getIdentifier() (o si cualquiera de tus bibliotecas lo hace, por ejemplo, AppCompat), significa que tu código está buscando nombres de recursos en función de las strings generadas de forma dinámica. Cuando lo haces, el reductor de recursos adopta un comportamiento defensivo de forma predeterminada y marca todos los recursos cuyos formatos de nombre coincidan como que están potencialmente en uso y no pueden quitarse.

Por ejemplo, el siguiente código hace que todos los recursos con el prefijo img_ se marquen como en uso.

Kotlin

    val name = String.format("img_%1d", angle + 1)
    val res = resources.getIdentifier(name, "drawable", packageName)
    

Java

    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

El reductor de recursos también revisa todas las constantes de strings de tu código (y en varios recursos res/raw/) en busca de URL de recursos en un formato similar a file:///android_res/drawable//ic_plus_anim_016.png. Si encuentra strings como esta, o bien otras que crea que puedan usarse para crear URL como esta, no las quitará.

Estos son ejemplos del modo de reducción seguro que se habilita de forma predeterminada. Sin embargo, puedes descartar esta estrategia "más vale prevenir que lamentar" y especificar que el reductor de recursos únicamente conserve los recursos que esté seguro de que se usan. Para ello, configura shrinkMode en strict, en el archivo keep.xml, de la siguiente manera:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:shrinkMode="strict" />
    

Si habilitas el modo de reducción estricta y tu código también hace referencia a recursos con strings generadas de forma dinámica, como se muestra más arriba, deberás conservar esos recursos de forma manual por medio del atributo tools:keep.

Cómo quitar recursos alternativos que no se usan

El reductor de recursos de Gradle solo quita recursos a los que no se hace referencia en el código de tu app, lo que significa que no se quitarán los recursos alternativos para las diferentes opciones de configuración del dispositivo. Si es necesario, puedes usar la propiedad resConfigs del complemento de Android para Gradle a fin de quitar los archivos de recursos alternativos que tu app no necesita.

Por ejemplo, si usas una biblioteca que incluye recursos de idioma (como AppCompat o los Servicios de Google Play), entonces tu APK incluirá todas las strings de idiomas traducidos para los mensajes en esas bibliotecas independientemente de si el resto de tu app está traducida en los mismos idioma o no. Si solo quieres conservar los idiomas que tu app admite oficialmente, puedes especificarlos mediante la propiedad resConfig. Se quitarán los recursos para idiomas que no se especifiquen.

En el siguiente fragmento de código, se muestra cómo limitar tus recursos de idioma a inglés y francés únicamente:

android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
    

Del mismo modo, puedes personalizar los recursos de ABI o las densidades de pantalla que quieras incluir en tu APK mediante la compilación de varios APK para que cada uno se oriente a una configuración del dispositivo diferente.

Cómo fusionar recursos duplicados

De forma predeterminada, Gradle también fusiona los recursos con nombres idénticos, como los elementos de diseño con el mismo nombre que puedan ubicarse en diferentes carpetas de recursos. La propiedad shrinkResources no controla este comportamiento, y este tampoco puede inhabilitarse, ya que es necesario para evitar que se produzcan errores cuando varios recursos coinciden con el nombre que tu código está buscando.

La fusión de recursos se produce cuando dos o más archivos comparten un nombre, un tipo y un calificador de recurso idénticos. Gradle selecciona el archivo que considera que es la mejor opción entre los duplicados (en función del orden de prioridad descrito debajo) y transmite solo ese recurso al APPT para su distribución en el archivo APK.

Gradle busca recursos duplicados en las siguientes ubicaciones:

  • Los recursos principales, asociados con el conjunto principal de fuentes, que por lo general se ubican en src/main/res/
  • Las superposiciones de variantes, de tipo de compilación y de versiones de compilación
  • Las dependencias del proyecto de biblioteca

Gradle fusiona recursos en el siguiente orden de prioridad escalonado:

Dependencias → Principal → Versión de compilación → Tipo de compilación

Por ejemplo, si un recurso duplicado aparece tanto en tus recursos principales como en un tipo de compilación, Gradle seleccionará el que se encuentre en el tipo de compilación.

Si aparecen recursos idénticos en el mismo conjunto de fuentes, Gradle no podrá fusionarlos y emitirá un error de fusión de recursos, lo que puede ocurrir si defines varios conjuntos de fuentes en la propiedad sourceSet de tu archivo build.gradle (por ejemplo, si src/main/res/ y src/main/res2/ contienen recursos idénticos).

Cómo ocultar tu código

El propósito de la ofuscación es reducir el tamaño de tu app acortando los nombres de las clases, de los métodos y de los campos de tu app. A continuación, se muestra un ejemplo de ofuscación mediante R8:

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
    androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
        android.content.Context mContext -> a
        int mListItemLayout -> O
        int mViewSpacingRight -> l
        android.widget.Button mButtonNeutral -> w
        int mMultiChoiceItemLayout -> M
        boolean mShowTitle -> P
        int mViewSpacingLeft -> j
        int mButtonPanelSideLayout -> K
    

Si bien la ofuscación no quita el código de tu app, podrás reducir significativamente su tamaño en apps con archivos DEX que indexan varias clases, métodos y campos. Sin embargo, como la ofuscación renombra diferentes partes de tu código, algunas tareas (como la inspección de seguimiento de pila) requieren herramientas adicionales. Para comprender el seguimiento de pila luego de la ofuscación, lee la siguiente sección sobre cómo decodificar una pila de seguimiento ofuscada.

De manera adicional, si tu código se basa en una asignación de nombres predictiva para los métodos y las clases de tu app, cuando uses la reflexión, por ejemplo, deberías tratar esas firmas como puntos de entrada y especificar reglas de conservación para ellas, como se describe en la sección sobre cómo determinar de forma personalizada el código que se conservará. Estas reglas de conservación le indican a R8 que no solo debe conservar ese código en el DEX final de tu app sino también mantener el nombre original.

Cómo decodificar un seguimiento de pila oculto

Una vez que R8 ofusca tu código, podría resultar difícil (si no imposible) comprender el seguimiento de pila porque es probable que se hayan cambiado los nombres de las clases y de los métodos. Además de la capacidad de renombrar, R8 también puede cambiar los números de línea presentes en los seguimientos de pila a fin de reducir aún más el tamaño al escribir los archivos DEX. Por suerte, R8 crea un archivo mapping.txt cada vez que se ejecuta, en el que los nombres de las clases, de los métodos y de los campos ofuscados están asignados a los nombres originales. Este archivo de asignación también contiene información para volver a asignar los números de línea a los números de línea de los archivos de fuente originales y R8 lo guarda en el directorio <module- name>/build/outputs/mapping/<build-type>/.

Cuando publicas tu app en Google Play, puedes subir el archivo mapping.txt para cada versión de tu APK. Luego, Google Play desofuscará los seguimientos de pila entrantes de los problemas informados por usuarios, de manera que puedas revisarlos en Google Play Console. Para obtener más información, consulta el artículo del Centro de ayuda sobre cómo desofuscar los seguimientos de pila de fallas.

Para convertir un seguimiento de pila desofuscado en uno que sea legible, usa la secuencia de comandos de ReTrace, que viene incluida con ProGuard.

Optimización de código

Si quieres reducir aún más tu app, R8 inspeccionará tu código en mayor profundidad a fin de quitar el código que no se usa o, cuando sea posible, volverá a escribirlo de manera que no sea tan detallado. A continuación, se muestran algunos ejemplos de tales optimizaciones:

  • Si tu código nunca toma la rama else {} para una declaración "if/else", es posible que R8 quite el código para la rama else {}.
  • Si tu código llama un método en un solo lugar, es posible que R8 quite el método y lo intercale en un único sitio de llamada.
  • Si R8 determina que una clase tiene una sola subclase única, y esta no se divide en instancias (por ejemplo, una clase de implementación concreta que solo usa una clase básica abstracta), entonces R8 puede combinar ambas clases y quitar una de la app.
  • Para obtener más información, lee la entrada de blog sobre la optimización de R8, de Jake Wharton.

R8 no te permite inhabilitar ni habilitar optimizaciones discretas, ni tampoco modificar el comportamiento de una optimización. De hecho, este ignora cualquier regla ProGuard que intenta modificar optimizaciones predeterminadas, como -optimizations y - optimizationpasses. Esta restricción es importante porque R8 continúa mejorando, y mantener un comportamiento estándar para las optimizaciones ayuda al equipo de Android Studio a solucionar fácilmente cualquier problema que puedas experimentar.

Cómo habilitar optimizaciones más agresivas

R8 incluye un conjunto de optimizaciones adicionales que no están habilitadas de forma predeterminada. Puedes habilitarlas mediante la inclusión de lo siguiente en el archivo gradle.properties de tu proyecto:

android.enableR8.fullMode=true
    

Como las optimizaciones adicionales hacen que R8 se comporte de manera diferente a ProGuard, es posible que estas requieran que incluyas reglas ProGuard adicionales a fin de evitar problemas en el tiempo de ejecución. Por ejemplo, supongamos que tu código hace referencia a una clase mediante la API de reflexión de Java. De manera predeterminada, R8 supone que quieres examinar y manipular los objetos de esa clase en el tiempo de ejecución (incluso si tu código no lo hace) y conserva automáticamente la clase y su inicializador estático.

Sin embargo, cuando usas el "modo completo", R8 no realiza esta suposición y, si determina que tu código nunca usa la clase en el tiempo de ejecución, la quitará del archivo DEX final de tu app. Es decir, si quieres conservar la clase y su inicializador estático, deberás incluir una regla de conservación en tu archivo de reglas.

Si tienes algún problema al usar el "modo completo" de R8, consulta la Página de preguntas frecuentes de R8 para obtener una posible solución. Si no puedes resolver el problema, informa un error.

Cómo solucionar problemas con R8

En esta sección, se describen algunas estrategias para solucionar problemas cuando habilitas la reducción, la ofuscación y la optimización con R8. Si no encuentras una solución para tu problema a continuación, también puedes leer la Página de preguntas frecuentes de R8 y la Guía de solución de problemas de ProGuard.

Cómo generar un informe sobre el código quitado (o conservado)

Para ayudarte a resolver determinados problemas de R8, podría resultarte útil ver un informe sobre todos los códigos que R8 quitó de tu app. En cada módulo para el que quieras generar este informe, agrega -printusage <output-dir>/usage.txt en tu archivo de reglas personalizado. Cuando habilites R8 y compiles tu app, R8 mostrará un informe con la ruta de acceso y el nombre de archivo que especificaste. El informe del código que se quitó es similar al siguiente:

androidx.drawerlayout.R$attr
    androidx.vectordrawable.R
    androidx.appcompat.app.AppCompatDelegateImpl
        public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
        public boolean hasWindowFeature(int)
        public void setHandleNativeActionModesEnabled(boolean)
        android.view.ViewGroup getSubDecor()
        public void setLocalNightMode(int)
        final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
        public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
        private static final boolean DEBUG
        private static final java.lang.String KEY_LOCAL_NIGHT_MODE
        static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
    ...
    

En cambio, si quieres ver un informe sobre los puntos de entrada que R8 determina desde las reglas de conservación de tu proyecto, incluye -printseeds <output-dir>/seeds.txt en tu archivo de reglas personalizado. Cuando habilites R8 y compiles tu app, R8 mostrará un informe con la ruta de acceso y el nombre de archivo que especificaste. El informe sobre los puntos de entrada conservados es similar al siguiente:

com.example.myapplication.MainActivity
    androidx.appcompat.R$layout: int abc_action_menu_item_layout
    androidx.appcompat.R$attr: int activityChooserViewStyle
    androidx.appcompat.R$styleable: int MenuItem_android_id
    androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
    androidx.lifecycle.FullLifecycleObserverAdapter
    ...
    

Cómo solucionar problemas de reducción de recursos

Cuando reduces recursos, en la ventana Build , se muestra un resumen de los recursos que se quitarán del APK.(Primero debes hacer clic en Toggle view  en el lado izquierdo de la ventana para mostrar la salida de texto detallada de Gradle). Por ejemplo:

:android:shrinkDebugResources
    Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
    :android:validateDebugSigning
    

Además, Gradle crea un archivo de diagnóstico llamado resources.txt en <module-name>/build/outputs/mapping/release/ (la misma carpeta donde se ubican los archivos de salida de ProGuard). Este archivo incluyen detalles, como qué recursos hacen referencia a otros recursos y cuáles de estos se usan o quitan.

Por ejemplo, para saber por qué @drawable/ic_plus_anim_016 aún está en tu APK, abre el archivo resources.txt y busca ese nombre de archivo. Es posible que descubras que se hace referencia a este desde otro recurso, como se muestra a continuación:

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
    16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016
    

Luego, debes saber por qué se puede acceder a @drawable/add_schedule_fab_icon_anim, y si buscas de manera ascendente, encontrarás que el recurso se enumera en "The root reachable resources are:", lo que significa que es una referencia de código a add_schedule_fab_icon_anim (es decir, se encontró su ID de elemento de diseño R en el código accesible).

Si no usas una comprobación estricta, los ID de recursos podrían marcarse como accesibles si hay constantes de strings que pareciera que se usan para crear los nombres de los recursos cargados de forma dinámica. En ese caso, si buscas la salida de la compilación del nombre de recurso, es posible que encuentres un mensaje como el siguiente:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
        used because it format-string matches string pool constant ic_plus_anim_%1$d.
    

Si solo ves una de esas strings y estás seguro de que no se usa para cargar el recurso determinado de forma dinámica, puedes usar el atributo tools:discard a fin de indicar al sistema de compilación que la quite, como se describe en la sección sobre cómo determinar de forma personalizada los recursos que se conservarán.