Configura y soluciona problemas de las reglas de conservación de R8
Lectura de 7 min
En el desarrollo moderno de Android, enviar una aplicación pequeña, rápida y segura es una expectativa fundamental de los usuarios. La herramienta principal del sistema de compilación de Android para lograrlo es el optimizador R8 , el compilador que controla el código inactivo y la eliminación de recursos para la reducción, el cambio de nombre o la reducción del código, y la optimización de la app.
Habilitar R8 es un paso fundamental para preparar una app para su lanzamiento, pero requiere que los desarrolladores proporcionen orientación en forma de "reglas de conservación".
Después de leer este artículo, mira el video de la Semana de Performance Spotlight sobre cómo habilitar, depurar y solucionar problemas del optimizador de R8 en YouTube.
Por qué se necesitan las reglas de conservación
La necesidad de escribir reglas de conservación surge de un conflicto central: R8 es una herramienta de análisis estático, pero las apps para Android suelen depender de patrones de ejecución dinámicos, como la reflexión o las llamadas dentro y fuera del código nativo con la JNI (interfaz nativa de Java).
R8 compila un gráfico del código usado analizando las llamadas directas. Cuando se accede al código de forma dinámica, el análisis estático de R8 no puede predecirlo y lo identificará como sin usar y lo quitará, lo que provocará fallas en el tiempo de ejecución.
Una regla de conservación es una instrucción explícita para el compilador de R8 que indica lo siguiente: "Esta clase, método o campo específico es un punto de entrada al que se accederá de forma dinámica durante el tiempo de ejecución. Debes conservarlo, incluso si no encuentras una referencia directa a él".
Consulta la guía oficial para obtener más detalles sobre las reglas de conservación.
Dónde escribir las reglas de conservación
Las reglas de conservación personalizadas para una aplicación se escriben en un archivo de texto. Por convención, este archivo se llama proguard-rules.pro y se encuentra en la raíz del módulo de la app o biblioteca. Luego, este archivo se especifica en el tipo de compilación release del archivo build.gradle.kts de tu módulo.
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}Usa el archivo predeterminado correcto
El método getDefaultProguardFile importa un conjunto predeterminado de reglas que proporciona el SDK de Android. Si usas el archivo incorrecto, es posible que tu app no se optimice. Asegúrate de usar proguard-android-optimize.txt. Este archivo proporciona las reglas de conservación predeterminadas para los componentes estándar de Android y habilita las optimizaciones de código de R8. La versión desactualizada de proguard-android.txt solo proporciona las reglas de conservación, pero no habilita las optimizaciones de R8.
Como se trata de un problema de rendimiento grave, comenzaremos a advertir a los desarrolladores sobre el uso del archivo incorrecto a partir de la actualización de funciones de Android Studio Narwhal 3. Además, a partir de la versión 9.0 del complemento de Android para Gradle, ya no admitiremos el archivo proguard-android.txt obsoleto. Por lo tanto, asegúrate de actualizar a la versión optimizada.
Cómo escribir reglas de conservación
Una regla de conservación consta de tres partes principales:
- Una opción como
-keepo-keepclassmembers - Modificadores opcionales como
allowshrinking - Una especificación de clase que define el código que debe coincidir
Para obtener la sintaxis completa y ejemplos, consulta la guía para agregar reglas de conservación.
Antipatrones de reglas de conservación
Es importante conocer las prácticas recomendadas, pero también los antipatrones. Estos antipatrones suelen surgir de malentendidos o atajos para solucionar problemas, y pueden ser catastróficos para el rendimiento de una compilación de producción.
Opciones globales
Estas marcas son parámetros de configuración globales que nunca deben usarse en una compilación de lanzamiento. Solo se usan para la depuración temporal con el objetivo de aislar un problema.
El uso de -dontotptimize inhabilita de manera eficaz las optimizaciones de rendimiento de R8, lo que genera una app más lenta.
Cuando usas -dontobfuscate, inhabilitas todos los cambios de nombre, y cuando usas -dontshrink, desactivas la eliminación de código no utilizado. Ambas reglas globales aumentan el tamaño de la app.
Siempre que sea posible, evita usar estas marcas globales en un entorno de producción para que la experiencia del usuario de la app sea más eficiente.
Reglas de conservación demasiado amplias
La forma más fácil de anular los beneficios de R8 es escribir reglas de conservación demasiado amplias. Las reglas de conservación como la que se muestra a continuación indican al optimizador de R8 que no reduzca, no ofusque y no optimice ninguna clase de este paquete ni ninguno de sus subpaquetes. Esto quita por completo los beneficios de R8 para todo ese paquete. En su lugar, intenta escribir reglas de conservación limitadas y específicas.
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMSEl operador de inversión (!)
El operador de inversión (!) parece una forma potente de excluir un paquete de una regla. Pero no es tan sencillo. Veamos este ejemplo.
-keep class !com.example.my_package.** { *; } // USE WITH CAUTIONTal vez pienses que esta regla significa "no conservar las clases encom.example.package", pero, en realidad, significa "conservar todas las clases, los métodos y las propiedades de toda la aplicación que no estén en com.example.package". Si esto te sorprende, te recomendamos que verifiques si hay negaciones en tu configuración de R8.
Reglas redundantes para componentes de Android
Otro error común es agregar manualmente reglas de conservación para los archivos Activities, Services o BroadcastReceivers de tu app. Esto es innecesario. El archivo proguard-android-optimize.txt predeterminado ya incluye las reglas pertinentes para que estos componentes estándar de Android funcionen de inmediato.
Además, muchas bibliotecas traen sus propias reglas de conservación. Por lo tanto, no deberías tener que escribir tus propias reglas para estos. En caso de que haya un problema con las reglas de conservación de una biblioteca que estés usando, lo mejor es que te comuniques con el autor de la biblioteca para ver cuál es el problema.
Prácticas recomendadas para las reglas de conservación
Ahora que sabes lo que no debes hacer, hablemos de las prácticas recomendadas.
Escribe reglas de conservación específicas
Las reglas de conservación adecuadas deben ser lo más restringidas y específicas posible. Solo deben conservar lo que sea necesario y permitir que R8 optimice todo lo demás.
| Regla | Calidad |
|---|---|
| Bajo: Conserva un paquete completo y sus subpaquetes. |
| Bajo: Conserva una clase completa que, probablemente, aún sea demasiado amplia. |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | Alto: Solo se conservan los métodos y las propiedades relevantes de una clase específica. |
Usa ancestros comunes
En lugar de escribir reglas de conservación independientes para varios modelos de datos diferentes, escribe una regla que se oriente a una clase o interfaz base común. La siguiente regla le indica a R8 que conserve cualquier miembro de las clases que implementen esta interfaz y es altamente escalable.
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}Usa anotaciones para segmentar varias clases
Crea una anotación personalizada (p.ej., @Serialize) y úsala para "etiquetar" las clases que necesitan que se conserven sus campos. Este es otro patrón limpio, declarativo y altamente escalable. También puedes crear reglas de conservación para las anotaciones ya existentes de los frameworks que usas.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}Elige la opción de conservación adecuada
La opción Conservar es la parte más importante de la regla. Elegir la opción incorrecta puede inhabilitar la optimización de forma innecesaria.
| Opción Keep | ¿Qué hace? |
-keep | Evita que se quiten o se cambien los nombres de la clase y los miembros mencionados en la declaración . |
-keepclassmembers | Impide que se quiten o se cambie el nombre de los miembros especificados, pero permite que se quite la clase en sí, aunque solo en las clases que no se quiten de otra manera. |
-keepclasseswithmembers | Una combinación: Conserva la clase y sus miembros, solo si están presentes todos los miembros especificados. |
Puedes obtener más información sobre la opción de conservar en nuestra documentación sobre las opciones de conservación.
Permitir la optimización con modificadores
Los modificadores como allowshrinking y allowobfuscation relajan una regla -keep amplia, lo que devuelve el poder de optimización a R8. Por ejemplo, si una biblioteca heredada te obliga a usar -keep en una clase completa, es posible que puedas recuperar cierta optimización permitiendo la reducción y la ofuscación:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
Agrega opciones globales para obtener una optimización adicional
Además de las reglas de conservación, puedes agregar marcas globales a tu archivo de configuración de R8 para fomentar aún más la optimización.
-repackageclasses es una opción potente que le indica a R8 que mueva todas las clases ofuscadas a un solo paquete. Esto ahorra mucho espacio en el archivo DEX, ya que quita las cadenas de nombres de paquetes redundantes.
-allowaccessmodification permite que R8 amplíe el acceso (p.ej., private a public) para habilitar una inserción más agresiva. Ahora, esta opción está habilitada de forma predeterminada cuando se usa proguard-android-optimize.txt.
Advertencia: Los autores de bibliotecas nunca deben agregar estas marcas de optimización global a sus reglas de consumidor, ya que se aplicarían de forma forzosa a toda la app.
Para que quede aún más claro, en la versión 9.0 del complemento de Android para Gradle, comenzaremos a ignorar por completo las marcas de optimización globales de las bibliotecas.
Prácticas recomendadas para bibliotecas
Todas las apps para Android dependen de bibliotecas de una forma u otra. Hablemos de las prácticas recomendadas para las bibliotecas.
Para desarrolladores de bibliotecas
Si tu biblioteca usa reflexión o JNI, tienes la responsabilidad de proporcionar las reglas de conservación necesarias a sus consumidores. Estas reglas se colocan en un archivo consumer-rules.pro, que luego se empaqueta automáticamente dentro del archivo AAR de la biblioteca.
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}Para consumidores de bibliotecas
Cómo filtrar las reglas de conservación problemáticas
Si debes usar una biblioteca que incluya reglas de conservación problemáticas, puedes filtrarlas en tu archivo build.gradle.kts a partir de AGP 9.0. Esto le indica a R8 que ignore las reglas que provienen de una dependencia específica.
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}La mejor regla de conservación es no tener ninguna
La estrategia definitiva de configuración de R8 es eliminar por completo la necesidad de escribir reglas de conservación. En muchas apps, esto se puede lograr eligiendo bibliotecas modernas que favorezcan la generación de código por sobre la reflexión. Con la generación de código, el optimizador puede determinar con mayor facilidad qué código se usa realmente en el entorno de ejecución y qué código se puede quitar. Además, no usar ninguna reflexión dinámica significa que no hay puntos de entrada "ocultos" y, por lo tanto, no se necesitan reglas de conservación. Cuando elijas una biblioteca nueva, siempre prefiere una solución que use la generación de código en lugar de la reflexión.
Para obtener más información sobre cómo elegir bibliotecas, consulta Elige la biblioteca con prudencia.
Depura y soluciona problemas de tu configuración de R8
Cuando R8 quite código que debería haber conservado o tu APK sea más grande de lo esperado, usa estas herramientas para diagnosticar el problema.
Cómo encontrar reglas de conservación duplicadas y globales
Como R8 combina reglas de decenas de fuentes, puede ser difícil saber cuál es el conjunto de reglas "final". Si agregas esta marca a tu archivo proguard-rules.pro, se generará un informe completo:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
Puedes buscar en este archivo para encontrar reglas redundantes o rastrear una regla problemática (como -dontoptimize) hasta la biblioteca específica que la incluyó.
Pregunta para R8: ¿Por qué conservas esto?
Si una clase que esperabas que se quitara todavía está en tu app, R8 puede decirte por qué. Solo agrega esta regla:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
Durante la compilación, R8 imprimirá la cadena exacta de referencias que provocó que se conservara esa clase, lo que te permitirá rastrear la referencia y ajustar tus reglas.
Para obtener una guía completa, consulta la sección Soluciona problemas de R8.
Próximos pasos
R8 es una herramienta potente para mejorar el rendimiento de las apps para Android. Su eficacia depende de que se comprenda correctamente su funcionamiento como motor de análisis estático.
Si escribes reglas específicas a nivel de miembro, aprovechas los ancestros y las anotaciones, y eliges con cuidado las opciones de conservación adecuadas, puedes preservar exactamente lo que es necesario. La práctica más avanzada es eliminar por completo la necesidad de reglas eligiendo bibliotecas modernas basadas en codegen en lugar de sus predecesoras basadas en reflexión.
Mientras sigues la Semana de Spotlight sobre el rendimiento, asegúrate de mirar el video de hoy en YouTube y continuar con nuestro desafío de R8. Usa #optimizationEnabled para cualquier pregunta sobre cómo habilitar o solucionar problemas de R8. Estamos aquí para ayudarte.
Es hora de que veas los beneficios por tu cuenta.
Te desafiamos a habilitar el modo completo de R8 para tu app hoy mismo.
- Sigue nuestras guías para desarrolladores y comienza: Habilita la optimización de apps.
- Verifica si aún usas
proguard-android.txty reemplázalo porproguard-android-optimize.txt. - Luego, mide el impacto. No solo sientas la diferencia, compruébala. Para medir las mejoras de rendimiento, adapta el código de nuestra app de ejemplo de Macrobenchmark en GitHub para medir los tiempos de inicio antes y después.
Estamos seguros de que notarás una mejora significativa en el rendimiento de tu app.
Mientras tanto, usa la etiqueta #AskAndroid en redes sociales para enviar tus preguntas. Durante la semana, nuestros expertos supervisarán y responderán tus preguntas.
No te pierdas el contenido de mañana, en el que hablaremos sobre la optimización guiada por perfiles con perfiles de Baseline y de inicio, compartiremos cómo mejoró el rendimiento de renderización de Compose en las versiones anteriores y compartiremos consideraciones de rendimiento para el trabajo en segundo plano.
-
Novedades de productosLas apps que se orienten a la próxima versión de Android ya no podrán escribir directamente en los contactos con el permiso WRITE_CONTACTS.
Roxanna Aliabadi Walker • Lectura de 6 min -
Novedades de productosA medida que se acelera el desarrollo basado en agentes, la ingeniería de Android alcanza una nueva y emocionante frontera: pasar de la finalización de código asistida a agentes que ejecutan tareas de extremo a extremo bajo tu dirección. Tienes total libertad y flexibilidad para elegir los modelos y agentes que usas.
Jason Buchel • Lectura de 3 min -
Novedades de productosHoy, compartimos cómo estamos llevando nuestra visión de “más que una tienda” al siguiente nivel. Desde la pestaña Tú, que ahora es la base personal de más de 250 millones de usuarios activos por mes, hasta nuestra plataforma de juegos unificada que conecta a más de 700 millones de perfiles de gamer, Google Play llega a los usuarios dondequiera que estén.
Paul Feng • Lectura de 3 min
Recibe la información más reciente sobre el desarrollo de Android en tu bandeja de entrada todas las semanas.