Optimización para autores de bibliotecas

Como autor de la 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. 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 desarrolladores de bibliotecas publicadas, pero también puede ser útil para 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 apps. Para obtener información sobre las bibliotecas que es apropiado usar, consulta Elige las bibliotecas con prudencia.

Usa 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 de plantillas 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. 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 durante el tiempo de ejecución. Como el código realmente 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 causa 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 se permite la reflexión

Si debes usar la reflexión, solo debes reflejar en cualquiera de las siguientes opciones:

  • Tipos de segmentación específicos (implementadores de interfaz o subclases específicos)
  • Código con una anotación de entorno 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 retención de consumidores segmentados.

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 expanden 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.

Cómo escribir reglas de retención del consumidor

Las bibliotecas deben empaquetar reglas de retención de "consumidor", que usan el mismo formato que las reglas de retención de apps. Estas reglas se agrupan en artefactos de bibliotecas (AAR o JAR) y se consumen automáticamente durante la optimización de apps para Android cuando se usa la biblioteca.

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 agrupar reglas con tu biblioteca de Kotlin/Java que se envía 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 de 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 archivo JAR de salida.

Verifica que el paquete JAR final combine las reglas correctamente. Para ello, comprueba que las reglas se encuentren en el directorio META-INF/proguard.

Cómo optimizar la compilación de bibliotecas AAR (avanzada)

Por lo general, no debes optimizar una compilación de biblioteca directamente, ya que las posibles optimizaciones en el tiempo de compilación de la biblioteca son muy limitadas. Solo durante una compilación de la 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 razonar sobre 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 tiempo de compilación, el complemento de Android para Gradle es compatible con esta opción.

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:

  • proguardFiles se usa en el tiempo de compilación, a menudo junto con getDefaultProguardFile("proguard-android-optimize.txt"), para definir qué parte de la biblioteca se debe conservar durante la compilación. Como mínimo, esta es tu API pública.
  • Por el contrario, consumerProguardFiles se empaqueta 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 reflexión para construir clases internas, es posible que debas definir las reglas de retenció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 (avanzada)

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 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 forma de 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 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 directorios 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 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).