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 debes 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 puede 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 Cómo habilitar la optimización de apps. Para obtener información sobre qué bibliotecas son adecuadas para usar, consulta Cómo elegir bibliotecas de forma inteligente.
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 (AAR o JAR) y se usan 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 tubuild.gradle.kts(obuild.gradle) archivo. Para obtener más información, consulta Cómo escribir reglas de conservación del consumidor. - 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 tubuild.gradle.kts(obuild.gradle) archivo. Para obtener más información, consulta Cómo optimizar 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 a la expansión de la app, el desperdicio de memoria, los inicios lentos y los errores de ANR (Application Not Responding). 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 inadecuadas: 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, Dagger2 y muchos otros.
Admite el modo completo de R8: Tu biblioteca no debería fallar cuando está habilitado 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 específico de reflexión o JNI 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 se usan anotaciones 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 es aceptable 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, cuando se inflan actividades, vistas y
elementos de diseño) 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: 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 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 se optimizó o se ofuscó cuando se compiló en un AAR (Android Archive) o JAR (Java Archive). 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 Cómo optimizar la compilación de bibliotecas 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 correcta.
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 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.
Para verificar que el JAR final agrupe las reglas correctamente, 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 se producen 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'.
Admite 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 mismo 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 directorios 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 se pueden usar en 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).