Como autor de una biblioteca, debes asegurarte de que los desarrolladores de apps puedan incorporarla fácilmente 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 una configuración adicional por parte del desarrollador, o bien debes documentar que la biblioteca podría no ser adecuada para su uso en Android. Es fundamental que las bibliotecas destinadas al uso en Android no impidan las 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 puede ser útil para desarrolladores de módulos de bibliotecas internas en una app modularizada grande.
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 es adecuado usar, consulta Elige las 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 todo lo que la biblioteca refleja. Si una biblioteca usa la 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 las 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 (AAR o JAR) 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 propiedad
consumerProguardFilesen tu archivobuild.gradle.kts(obuild.gradle). Para obtener más información, consulta Cómo escribir reglas de conservación para consumidores. - Las reglas de conservación de compilación de bibliotecas se aplican cuando se compila la biblioteca. Solo
son necesarios 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 propiedad
proguardFilesen tu archivobuild.gradle.kts(obuild.gradle). Para obtener más información, consulta Cómo optimizar la compilación de la biblioteca de 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 del objeto binario final de la app que las consume. Además de las prácticas recomendadas generales para las reglas de conservación, 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 del tamaño de la app, el desperdicio de memoria, los inicios lentos y los errores de ANR (Aplicación no responde). Las bibliotecas deben evitar infringir los siguientes requisitos para no reducir significativamente la calidad de la app ni la experiencia del usuario.
No uses reglas de conservación amplias o que abarquen todo el 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 las fallas a corto plazo, pero aumentan el tamaño de todas las apps que consumen tu biblioteca.
No incluyas reglas de conservación para todo el paquete (como
-keep class com.mylibrary.** {*; }) en tu biblioteca ni en otras bibliotecas de referencia. Estas reglas limitan la optimización de estos paquetes en todas las apps que consumen tu biblioteca.No incluyas reglas de conservación amplias para las dependencias de terceros que consume tu biblioteca (por ejemplo,
-keep public class com.google.android.gms.** { *; }). Muchas bibliotecas externas ya envían sus propias reglas de conservación de consumidores. Si se introduce una regla para todo el paquete, se reemplazan esas reglas y se fuerza a R8 a retener el código de dependencia sin referencia en toda la aplicación.No uses 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. La generación de código y la reflexión son enfoques comunes para evitar el código estándar cuando se programa, pero la generación de código es más compatible con un optimizador de apps como R8.
Con la generación de código, este se analiza y modifica durante el proceso de compilación. Debido a que 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 entorno de ejecución. Como el código no se finaliza realmente 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 ver un punto de entrada común, que usan Room, Hilt y muchos otros.
Admite el modo completo de R8: Tu biblioteca no debería fallar cuando el modo completo de R8 esté habilitado. El modo completo de R8 es el recomendado para usar R8 y es el predeterminado desde el AGP 8.0, que se estabilizó en 2023. Si tu biblioteca falla en R8, la solución es identificar el punto de entrada específico de reflexión o JNI y agregar una regla segmentada, no conservar todo el paquete. Para generar reglas orientadas a partir de anotaciones en estos puntos de entrada, usa Jetpack Keep.
Recomendaciones adicionales
Además de los requisitos de optimización, se incluyen las siguientes recomendaciones adicionales.
No uses
-repackageclassesen el archivo de reglas de conservación del consumidor de tu biblioteca. Las reglas de conservación del consumidor se combinan en la configuración de R8 de cada app que usa tu biblioteca, por lo que esta opción global se aplica a toda la app y anula la configuración de reempaquetado del desarrollador de apps. De forma predeterminada, a partir de AGP 9.0, la publicación de una biblioteca falla si sus reglas de conservación del consumidor contienen opciones globales. Para obtener más información, consulta las notas de la versión de AGP 9.0.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 la compilación de tu biblioteca. Esto puede mejorar la eficiencia de tu biblioteca en apps no optimizadas, pero, por lo general, no es necesario porque las apps también deben optimizarse.Declara los atributos que necesites para que tu biblioteca funcione en los archivos de reglas de conservación de la 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 la biblioteca y no en el archivo de reglas de conservación del consumidor de la biblioteca:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
Los autores de bibliotecas deben mantener el atributo
RuntimeVisibleAnnotationsen sus reglas de conservación de consumidores si se usan anotaciones en el entorno de ejecución.Los autores de bibliotecas no deben usar las siguientes opciones globales en sus reglas de conservación de consumidores. Al igual que
-repackageclasses, estas opciones configuran toda la compilación de R8 de la aplicación, en lugar de solo el código de tu biblioteca, por lo que debes dejarlas para el desarrollador de apps:-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Cuándo es aceptable la reflexión
Si debes usar la reflexión, solo debes reflejar en una de las siguientes opciones:
- Tipos de segmentación específicos (implementadores de interfaces o subclases específicos)
- Código que usa una anotación de entorno de ejecución específica
Usar la reflexión de esta manera limita el costo del entorno de ejecución y permite escribir reglas de conservación de consumidores segmentados.
Esta forma específica y orientada de reflexión es un patrón que puedes ver tanto en el framework de Android (por ejemplo, cuando se aumentan las actividades, las vistas y los elementos de diseño) como en las bibliotecas de AndroidX (por ejemplo, cuando se construye 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
Algunas ideas erróneas comunes pueden llevarte a configurar R8 de forma incorrecta. Se incluyen las siguientes:
Comprensión incorrecta de las optimizaciones de R8: Al contrario 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 la descripción general de la optimización de R8.
Omitir la optimización de bibliotecas ofuscadas: Un error común es omitir una biblioteca de la optimización, ya que 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 debería inhabilitar la optimización de la biblioteca incluyéndola en una regla de conservación. Para obtener más información, consulta Cómo optimizar la compilación de bibliotecas de AAR.
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 adecuada.
Valida la calidad de la regla 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, luego, inspecciona 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 de consumidor limitan la optimización en grandes partes del código de tu biblioteca, esto indica que tus reglas de consumidor necesitan un perfeccionamiento. Restringe las reglas de 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 la biblioteca.
Si usas Jetpack Keep para declarar el uso de reflexión y JNI de tu biblioteca, su complemento de Gradle puede empaquetar las reglas generadas como reglas de conservación del consumidor.
Bibliotecas de AAR
Para agregar reglas de consumidor a una biblioteca de 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 empaquetar reglas con tu biblioteca de Kotlin o Java que se envía como un JAR, coloca tu
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 empaquetarán en la ubicación correcta en tu JAR de salida.
Verifica que el JAR final agrupe las reglas correctamente comprobando que estén
en el directorio META-INF/proguard.
Optimiza la compilación de la biblioteca de AAR (avanzado)
Por lo general, no deberías optimizar una compilación de biblioteca directamente, ya que las optimizaciones posibles en el momento de la compilación de la biblioteca son muy limitadas. Como desarrollador de bibliotecas, debes considerar varias etapas de optimización y mantener el comportamiento, tanto en el momento de la compilación de la biblioteca como de la app, antes de optimizar esa biblioteca.
Si aún quieres optimizar tu biblioteca durante 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 del de
consumerProguardFiles:
- Las anotaciones
proguardFilesse usan en el momento de la compilación, a menudo junto congetDefaultProguardFile("proguard-android-optimize.txt"), para definir qué parte de la biblioteca se debe conservar durante la compilación de la biblioteca. Como mínimo, esta es tu API pública. - Por el contrario, los
consumerProguardFilesse empaquetan en la biblioteca para afectar las optimizaciones que se realizan 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'.
Compatibilidad con diferentes versiones de R8 (avanzado)
Puedes adaptar las reglas para que se orienten a versiones específicas de R8. Esto permite que tu biblioteca funcione de manera óptima en proyectos que usan versiones más recientes de R8, a la vez que permite que las reglas existentes se sigan usando 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 de r8-from-<X>-upto-<Y> para indicar las versiones de R8 para las que se escriben las reglas. Cada subdirectorio puede tener uno o
más archivos que contengan las reglas de R8, con cualquier nombre y extensión de archivo.
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. Las reglas del directorio r8 se pueden usar en cualquier versión de R8. R8 puede usar las reglas del directorio r8-from-8.0.0-upto-8.2.0 desde la versión 8.0.0 hasta la versión 8.2.0, 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
orientadas, 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).