Optimización para autores de bibliotecas

Como autor de una biblioteca, debes asegurarte de que los desarrolladores de apps puedan incorporar fácilmente tu biblioteca a sus apps y, al mismo tiempo, mantener una experiencia del usuario final de alta calidad. Debes asegurarte de que tu biblioteca sea compatible con la optimización de Android sin configuración adicional, o bien documentar que la biblioteca podría ser inadecuada para su uso en Android.

Esta documentación está dirigida a los desarrolladores de bibliotecas publicadas, pero también puede ser útil para los desarrolladores de módulos de bibliotecas internas 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 la app. Para obtener información sobre qué bibliotecas son adecuadas para usar, consulta Elige bibliotecas con prudencia.

Usar codegen en lugar de reflexión

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 codegen, el código se analiza y modifica durante el proceso de compilación. Dado 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 tiempo de ejecución. Dado que 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 obtener un punto de entrada común que usan Room, Dagger2 y muchos otros.

Cuándo es aceptable la reflexión

Si debes usar la reflexión, solo debes reflejarte en uno de los siguientes elementos:

  • Tipos segmentados específicos (implementadores o subclases de interfaces específicos)
  • Código que usa una anotación de tiempo de ejecución específica

Usar la reflexión de esta manera limita el costo del tiempo de ejecución y permite escribir reglas de conservación de consumidores 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.

Tipos de reglas de conservación en las bibliotecas

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 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 reglas de conservación del consumidor, que usan el mismo formato que las reglas de conservación de la app. Estas reglas se incluyen en los artefactos de la 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 consumerProguardFiles en tu archivo build.gradle.kts (o build.gradle). 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 la biblioteca se aplican cuando se compila la biblioteca. Solo son necesarios si decides optimizar parcialmente tu biblioteca en el tiempo de 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 proguardFiles en tu archivo build.gradle.kts (o build.gradle). Para obtener más información, consulta Cómo optimizar la compilación de la biblioteca de AAR.

Escribe reglas de conservación del consumidor

Además de la orientación general sobre las reglas de conservación, las siguientes son recomendaciones específicas para los autores de bibliotecas.

  • No uses reglas globales inapropiadas. Evita colocar parámetros de configuración globales, como -dontobfuscate o -allowaccessmodification, en el archivo de reglas de conservación del consumidor de tu biblioteca, ya que afectan a todas las apps que la usan.
  • No uses -repackageclasses en el archivo de reglas de conservación del consumidor de tu biblioteca. Sin embargo, para optimizar la compilación de tu biblioteca, puedes usar -repackageclasses con 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 hacer que tu biblioteca sea más eficiente, incluso si las apps que la consumen no están optimizadas, pero, en general, no es necesario, ya que las apps también deberían optimizarse. Para obtener más detalles sobre la optimización de bibliotecas, consulta Optimización para autores de bibliotecas.
  • Declara todos 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 la compilación de la biblioteca y no en el archivo de reglas de conservación del consumidor de la biblioteca:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Los autores de bibliotecas deben conservar el atributo RuntimeVisibleAnnotations en 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
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Bibliotecas AAR

Para agregar reglas de consumidor para 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 incluir reglas en tu biblioteca de Kotlin/Java que se entrega como un archivo JAR, coloca el archivo de reglas en el directorio META-INF/proguard/ del archivo 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 incluirán en la ubicación correcta de tu archivo JAR de salida.

Verifica que las reglas del paquete JAR final se agrupen correctamente. Para ello, comprueba que las reglas estén en el directorio META-INF/proguard.

Optimiza la compilación de la biblioteca AAR (avanzado)

En general, no es necesario optimizar la compilación de una biblioteca directamente, ya que las posibles optimizaciones en el tiempo de compilación de la biblioteca son muy limitadas. Solo durante la compilación de una aplicación, cuando se incluye una biblioteca como parte de una aplicación, R8 puede saber cómo se usan todos los métodos de la biblioteca y qué parámetros se pasan. Como desarrollador de bibliotecas, debes tener en cuenta varias etapas de optimización y mantener 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 momento de la 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:

  • proguardFiles se usan en el momento de la compilación, a menudo junto con getDefaultProguardFile("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.
  • En cambio, los consumerProguardFiles se 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 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, a la vez que permite que se sigan usando las reglas existentes 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 formato r8-from-<X>-upto-<Y> para indicar para qué versiones de R8 se escribieron 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. 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 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).