Como autor de bibliotecas, debes asegurarte de que los desarrolladores de apps puedan incorporar fácilmente tu biblioteca en sus apps y, al mismo tiempo, mantener una experiencia del usuario final de alta calidad. Esto significa que tu biblioteca debe ser compatible con la optimización de Android (R8) sin requerir configuración adicional por parte del desarrollador, o bien documentar que la biblioteca podría no ser adecuada para su uso en Android. Es fundamental que las bibliotecas destinadas a usarse en Android no impidan optimizaciones importantes de la app y cumplan con los requisitos de optimización adicionales.
Esta documentación está dirigida a desarrolladores de bibliotecas publicadas, pero también podría ser útil para desarrolladores de módulos de biblioteca internos en una app grande y modularizada.
Si eres desarrollador de apps y quieres obtener información para optimizar tu app para Android, consulta Habilita la optimización de apps. Para obtener información sobre qué bibliotecas son adecuadas para usar, consulta Elige bibliotecas con prudencia.
Comprende los tipos de reglas de conservación
Existen dos tipos distintos de reglas de conservación que puedes tener en las bibliotecas:
- Las reglas de conservación del consumidor deben especificar reglas que conserven lo que la biblioteca refleje. Si una biblioteca usa reflexión o JNI para llamar a su código, o al código definido por una app cliente, estas reglas deben describir qué código se debe conservar. Las bibliotecas deben empaquetar reglas de conservación del consumidor, que usan el mismo formato que las reglas de conservación de la app. Estas reglas se agrupan en artefactos de biblioteca (AARs o JARs) y se consumen automáticamente durante la optimización de la app para Android cuando se usa la biblioteca. Estas reglas se mantienen en el
archivo especificado con la
consumerProguardFilespropiedad en subuild.gradle.kts(obuild.gradle) archivo. Para obtener más información, consulta Recomendaciones adicionales. - Las reglas de conservación de compilación de biblioteca se aplican cuando se compila la biblioteca. Solo son necesarias si decides optimizar parcialmente tu biblioteca en el momento de la compilación. Deben evitar que se quite la API pública de la biblioteca; de lo contrario, la API pública no estará presente en la distribución de la biblioteca, lo que significa que los desarrolladores de apps no podrán usarla. Estas reglas se mantienen en el archivo
especificado con la
proguardFilespropiedad en subuild.gradle.kts(obuild.gradle) archivo. Para obtener más información, consulta Optimiza la compilación de bibliotecas AAR.
Requisitos y lineamientos de optimización
La configuración de R8 en las bibliotecas tiene un impacto global en el tamaño y el rendimiento binarios finales de la app que consume. Además de las prácticas recomendadas generales de las reglas de conservación keep rule best practices, los autores de bibliotecas deben cumplir con requisitos específicos y tener en cuenta lineamientos adicionales.
Cumple con los requisitos de optimización
La ineficiencia en las bibliotecas es un factor importante que contribuye al aumento de la app, el desperdicio de memoria, los inicios lentos y los errores de ANR (la aplicación no responde). Las bibliotecas deben evitar incumplir los siguientes requisitos para no reducir significativamente la calidad de la app y la experiencia del usuario.
No hay reglas de conservación amplias ni a nivel del paquete: Tu biblioteca no debe incluir reglas de conservación amplias que conserven la mayor parte del código de tu biblioteca o de otra biblioteca. Las reglas de conservación amplias pueden resolver fallas a corto plazo, pero aumentan el tamaño de la app de todas las apps que consumen tu biblioteca.
No incluyas reglas de conservación a nivel del paquete (como
-keep class com.mylibrary.** {*; }) para paquetes en tu biblioteca ni en otras bibliotecas a las que se haga referencia. Estas reglas limitan la optimización de estos paquetes en todas las apps que consumen tu biblioteca.No hay reglas globales inapropiadas: Nunca uses opciones globales como
-dontobfuscateo-allowaccessmodification.Uso de codegen en lugar de reflexión siempre que sea posible: Cuando sea posible, usa la generación de código (codegen) en lugar de la reflexión. Codegen y la reflexión son enfoques comunes para evitar el código estándar cuando se programa, pero codegen es más compatible con un optimizador de apps como R8.
Con codegen, el código se analiza y modifica durante el proceso de compilación. Como no hay modificaciones importantes después del tiempo de compilación, el optimizador sabe qué código se necesita en última instancia y qué se puede quitar de forma segura.
Con la reflexión, el código se analiza y manipula en el tiempo de ejecución. Como el código no se finaliza hasta que se ejecuta, el optimizador no sabe qué código se puede quitar de forma segura. Es probable que quite el código que se usa de forma dinámica a través de la reflexión durante el tiempo de ejecución, lo que provoca fallas en la app para los usuarios.
Muchas bibliotecas modernas usan codegen en lugar de reflexión. Consulta KSP para obtener un punto de entrada común que usan Room, Hilt y muchos otros.
Compatibilidad con el modo completo de R8: Tu biblioteca no debería fallar cuando se habilita el modo completo de R8. El modo completo de R8 es el modo recomendado para usar R8 y es el predeterminado desde AGP 8.0, que se estabilizó en 2023. Si tu biblioteca falla en R8, la solución es identificar el punto de entrada de reflexión o JNI específico y agregar una regla segmentada, no conservar todo el paquete.
Recomendaciones adicionales
Además de los requisitos de optimización, estas son recomendaciones adicionales.
- No uses
-repackageclassesen el archivo de reglas de conservación del consumidor de tu biblioteca. Sin embargo, para optimizar la compilación de tu biblioteca, puedes usar-repackageclassescon un nombre de paquete interno, como<your.library.package>.internal, en el archivo de reglas de conservación de compilación de tu biblioteca. Esto puede mejorar la eficiencia de tu biblioteca en apps no optimizadas. Sin embargo, por lo general, no es necesario, ya que las apps también deben optimizarse. - Declara cualquier atributo que necesites para que tu biblioteca funcione en los archivos de reglas de conservación de tu biblioteca, incluso si puede haber una superposición con los atributos definidos en
proguard-android-optimize.txt. - Si necesitas los siguientes atributos en la distribución de tu biblioteca, mantenlos en el archivo de reglas de conservación de compilación de tu biblioteca y no en el archivo de reglas de conservación del consumidor de tu biblioteca:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Los autores de bibliotecas deben conservar el atributo
RuntimeVisibleAnnotationsen sus reglas de conservación del consumidor si las anotaciones se usan en el tiempo de ejecución. - Los autores de bibliotecas no deben usar las siguientes opciones globales en sus reglas de conservación del consumidor:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Cuándo está bien usar la reflexión
Si debes usar la reflexión, solo debes reflejarte en cualquiera de los siguientes:
- Tipos segmentados específicos (implementadores o subclases de interfaz específicos)
- Código que usa una anotación de tiempo de ejecución específica
El uso de la reflexión de esta manera limita el costo del tiempo de ejecución y permite escribir reglas de conservación del consumidor segmentadas.
Esta forma específica y segmentada de reflexión es un patrón que puedes ver en
el framework de Android (por ejemplo, durante la carga de recursos del sistema)
y en las bibliotecas de AndroidX (por ejemplo, cuando se construyen WorkManager
ListenableWorkers, o RoomDatabases). Por el contrario, la reflexión abierta
de Gson no es adecuada para su uso en apps para Android.
Conceptos erróneos comunes
Algunos conceptos erróneos comunes pueden llevarte a configurar R8 de forma incorrecta. Estos incluyen lo siguiente:
Comprensión incorrecta de las optimizaciones de R8: A diferencia de lo que se cree, las optimizaciones de R8 no se limitan solo a la ofuscación, sino que también incluyen la reducción de código y las optimizaciones lógicas con técnicas de intercalación de métodos y combinación de clases. Para obtener más información, consulta Descripción general de la optimización de R8.
Omisión de la optimización de bibliotecas ofuscadas: Un error común es omitir una biblioteca de la optimización, ya que la biblioteca se optimizó o se ofuscó cuando se compiló en un AAR (archivo de Android) o JAR (archivo de Java). Las optimizaciones durante el tiempo de compilación de la biblioteca son limitadas, y tu app no debe inhabilitar la optimización de la biblioteca incluyéndola en una regla de conservación. Para obtener más información, consulta Descripción general de la optimización de R8.
Comprensión incorrecta de la opción
-keep: La regla-keepimpide que R8 ejecute cualquiera de sus pases de optimización. Para obtener más información, consulta Elige la opción de conservación correcta.
Valida la calidad de las reglas de conservación del consumidor
Para validar que tu biblioteca no impida demasiada optimización de código cuando se incorpora en una app, usa una muestra de biblioteca o una app de prueba de integración. Habilita R8 y analiza la calidad general de la configuración de R8 de la app con el Analizador de configuración de R8.
Si se muestra que tus reglas del consumidor limitan la optimización en grandes partes del código de tu biblioteca, esto indica que tus reglas del consumidor necesitan refinamiento. Reduce las reglas del consumidor y prueba los aspectos afectados de tu biblioteca en una app con R8 habilitado.
Configura el empaquetado de reglas
Para asegurarte de que las reglas de conservación del consumidor se apliquen correctamente, debes empaquetarlas de forma adecuada según el formato de tu biblioteca.
Bibliotecas AAR
Para agregar reglas del consumidor a una biblioteca AAR, usa la opción consumerProguardFiles en la secuencia de comandos de compilación del módulo de la biblioteca de Android. Para obtener más información, consulta nuestra
guía para crear módulos de biblioteca.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Bibliotecas JAR
Para agrupar reglas con tu biblioteca de Kotlin o Java que se envía como un JAR, coloca el archivo de reglas en el directorio META-INF/proguard/ del JAR final, con cualquier nombre de archivo.
Por ejemplo, si tu código está en <libraryroot>/src/main/kotlin, coloca un archivo de reglas del consumidor
en
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
y las reglas se agruparán en la ubicación correcta en tu JAR de salida.
Verifica que el JAR final agrupe las reglas correctamente. Para ello, comprueba que las reglas estén en el directorio META-INF/proguard.
Optimiza la compilación de bibliotecas AAR (avanzado)
Por lo general, no es necesario optimizar una compilación de biblioteca directamente porque las optimizaciones posibles en el tiempo de compilación de la biblioteca son muy limitadas. Como desarrollador de bibliotecas, debes razonar sobre varias etapas de optimización y conservar el comportamiento, tanto en el tiempo de compilación de la biblioteca como de la app, antes de optimizar esa biblioteca.
Si aún quieres optimizar tu biblioteca en el tiempo de compilación, el complemento de Android para Gradle lo admite.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Ten en cuenta que el comportamiento de proguardFiles es muy diferente de consumerProguardFiles:
proguardFilesse usan en el tiempo de compilación, a menudo junto congetDefaultProguardFile("proguard-android-optimize.txt"), para definir qué parte de tu biblioteca se debe conservar durante la compilación de la biblioteca. Como mínimo, esta es tu API pública.- Por el contrario,
consumerProguardFilesse empaquetan en la biblioteca para afectar las optimizaciones que ocurren más adelante, durante la compilación de una app que consume tu biblioteca.
Por ejemplo, si tu biblioteca usa la reflexión para construir clases internas, es posible que debas definir las reglas de conservación en proguardFiles y consumerProguardFiles.
Si usas -repackageclasses en la compilación de tu biblioteca, vuelve a empaquetar las clases en un subpaquete dentro del paquete de tu biblioteca. Por ejemplo, usa -repackageclasses
'com.example.mylibrary.internal' en lugar de -repackageclasses 'internal'.
Optimiza el rendimiento de la IU de Compose
Si tu biblioteca incluye componentes de la IU de Compose, optimiza la reducción binaria con R8 (como se describió anteriormente) y la eficiencia de la recomposición:
- Diseña elementos componibles estables: Para permitir que el compilador omita de forma segura tus elementos componibles cuando las entradas no hayan cambiado, estructura tus modelos de datos y configuraciones de biblioteca para seguir las reglas de estabilidad. Para obtener más información, consulta Estabilidad en Compose.
- Revisa las métricas del compilador: Usa las métricas del compilador de Compose para verificar que los puntos de entrada de tu biblioteca estén marcados como reiniciables y omitibles. Esto minimiza la sobrecarga de recomposición para las apps del consumidor. Para obtener más información, consulta Diagnostica problemas de estabilidad.
Compatibilidad con diferentes versiones de R8 (avanzado)
Puedes adaptar las reglas para segmentar versiones específicas de R8. Esto permite que tu biblioteca funcione de manera óptima en proyectos que usan versiones más recientes de R8, al tiempo que permite que las reglas existentes sigan usándose en proyectos con versiones anteriores de R8.
Para especificar reglas de R8 segmentadas, debes incluirlas en el directorio META-INF/com.android.tools dentro de classes.jar de un AAR o en el directorio META-INF/com.android.tools de un JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
En el directorio META-INF/com.android.tools, puede haber varios
subdirectorios con nombres en el formato r8-from-<X>-upto-<Y> para indicar
para qué versiones de R8 se escriben las reglas. Cada subdirectorio puede tener uno o más archivos que contengan las reglas de R8, con cualquier nombre de archivo y extensión.
Ten en cuenta que las partes -from-<X> y -upto-<Y> son opcionales, la versión <Y>
es exclusiva y los rangos de versiones suelen ser continuos, pero también pueden
superponerse.
Por ejemplo, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 y
r8-from-8.2.0 son nombres de directorio que representan un conjunto de reglas de R8 segmentadas. Cualquier versión de R8 puede usar las reglas del directorio r8. Las reglas del directorio r8-from-8.0.0-upto-8.2.0 pueden usarse en R8 desde la versión 8.0.0 hasta la versión 8.2.0, pero sin incluirla.
El complemento de Android para Gradle usa esa información para seleccionar todas las reglas que puede usar la versión actual de R8. Si una biblioteca no especifica reglas de R8
segmentadas, el complemento de Android para Gradle seleccionará las reglas de las ubicaciones heredadas
(proguard.txt para un AAR o META-INF/proguard/<ProGuard-rule-files> para un
JAR).