Optimiza el código y los recursos de la plataforma con R8

El sistema de compilación de la plataforma de Android (Soong) ejecuta el compilador R8 en destinos que compilan código de bytes DEX, incluidas las apps para Android (android_app), las pruebas (android_test) y las bibliotecas de Java instalables con compile_dex: true (como services.jar), para reducir, optimizar y quitar el código y los recursos que no se usan. En las bibliotecas estáticas (android_library y java_library estática), Soong no ejecuta R8 directamente, sino que usa el bloque de propiedades optimize para adjuntar y propagar reglas de conservación del consumidor (export_proguard_flags_files: true) a los destinos DEX descendentes que las vinculan de forma estática.

Para los ingenieros de plataformas que compilan imágenes del sistema o paquetes de proveedores, eliminar las reglas de conservación demasiado amplias ofrece beneficios directos para el estado del sistema:

  • Menor espacio de la partición del sistema: R8 quita las clases, los métodos y los recursos inactivos antes de empaquetar los APK y los JAR en /system, /system_ext, /product y /vendor.
  • Artefactos compilados más pequeños: Menos métodos DEX significan archivos .odex y .vdex más pequeños generados por dex2oat durante el tiempo de compilación o la compilación integrado en el dispositivo.
  • Menor presión de memoria durante el tiempo de ejecución: Debido a que Android asigna .odex, .vdex y tablas de recursos de APK a la memoria del proceso con llamadas mmap de paginación por demanda, los objetos binarios más pequeños reducen los errores de página importantes durante el inicio de la app y disminuyen el código residente y la memoria de recursos en los procesos. Para obtener más información sobre cómo el código de la app compilado afecta la memoria del dispositivo, consulta El código de la app es memoria.
  • Optimización más eficaz de todo el programa: Las restricciones de conservación más estrictas permiten que R8 incorpore métodos, desvirtualice interfaces y llamadas virtuales, elimine campos sin usar y propague constantes en todas las clases.

Cómo configurar la optimización de R8 en Soong

En los archivos Android.bp, configura la optimización de R8 con el bloque de propiedades optimize en los destinos android_app y java_library instalables (o en los módulos android_library y java_library estáticos para exportar reglas de conservación del consumidor):

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

En la siguiente tabla, se resumen las propiedades de optimize más comunes en Soong (definidas en build/soong/java/dex.go):

Propiedad Descripción
enabled Controla si R8 se ejecuta en el destino. La configuración predeterminada es true para todos los destinos de android_app.
shrink Controla la eliminación de código muerto para quitar clases, campos y métodos inaccesibles. El valor predeterminado es true para los destinos android_app (false para los módulos de prueba y java_library independientes, que pasan -dontshrink, a menos que se establezcan de forma explícita).
optimize Controla las optimizaciones de código de bytes, como la inserción de métodos, la combinación de clases, la propagación de constantes y la eliminación de ramas inactivas. El valor predeterminado es true para los destinos android_app (controlados por la marca de compilación de lanzamiento RELEASE_R8_OPTIMIZE_BY_DEFAULT).
obfuscate Controla la reducción y el cambio de nombre de los identificadores. El valor predeterminado es false para la compatibilidad histórica, pero configúralo como true siempre que sea posible para reducir el tamaño de DEX y desbloquear optimizaciones más profundas (consulta Habilita la ofuscación siempre que sea posible).
shrink_resources Quita los recursos sin usar (entradas res/) del APK empaquetado después de la reducción de código. La configuración predeterminada es false.
optimized_shrink_resources Ejecuta la canalización integrada del reductor de código y recursos de R8 para que este realice un seguimiento de las referencias de código y recursos de forma conjunta en un solo paso. Cuando se configura shrink_resources: true, el valor predeterminado es RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true en las compilaciones de plataforma estándar).
proguard_flags_files Enumera los archivos .flags o .pro específicos del módulo que contienen reglas de conservación personalizadas. Evita agregar archivos personalizados cuando las anotaciones estándar o los valores predeterminados sean suficientes.
export_proguard_flags_files Propaga el proguard_flags_files de esta biblioteca a los módulos descendentes que dependen de ella de forma estática.
trace_references_from Enumera los destinos dependientes de la compilación de DEX directa (conectados a través de instrumentation_for o libs) cuyas referencias de código de bytes en este destino R8 rastrea y retiene automáticamente (también se propagan en las variantes override_android_app y override_android_test, y se admiten para bibliotecas de Java complementarias no dependientes cuando se vinculan con una división .impl).
allow_traced_references Permite que los destinos dependientes de la compilación DEX directa (conectados a través de instrumentation_for o libs, como pruebas de proveedores o entre repositorios que no se pueden incluir en trace_references_from) habiliten el seguimiento de sus referencias de código de bytes en este destino configurando trace_references: true.
trace_references Cuando se establece en true en un dependiente de compilación de DEX directo de un destino con allow_traced_references: true, ejecuta R8 tracereferences en este módulo y pasa las reglas de conservación generadas al módulo de destino.
protect_api_surface Pasa --protect-api-surface a R8. Cuando se establece en un java_sdk_library, también genera y adjunta automáticamente reglas de conservación de la API de Metalava en todos los permisos de la API habilitados (público, sistema, module-lib, system-server y prueba) a la biblioteca de implementación.

Las bibliotecas estáticas (android_library, java_library y las importaciones de AAR) también pueden establecer required_downstream_optimization: { enabled: true, optimize: true } en Android.bp para requerir cualquier destino de compilación de DEX descendente que las vincule de forma estática (directa o transitivamente) para habilitar la reducción de R8 y la optimización de código de bytes. Soong habilita esto automáticamente en androidx.compose.runtime_runtime para que los destinos que vinculan Jetpack Compose se optimicen con R8.

Habilita la ofuscación siempre que sea posible.

En Soong, obfuscate se establece de forma predeterminada en false para la compatibilidad histórica, pero debes establecer de forma explícita obfuscate: true en android_app y los destinos DEX independientes siempre que sea posible:

  • Menor espacio de DEX y .vdex: Cambiar el nombre de los paquetes, las clases, los campos y los métodos por identificadores cortos (a, b) reduce el grupo de cadenas de DEX (string_ids y string_data_item) y los descriptores de tipo. Debido a que Android asigna archivos .vdex y .odex a la memoria del proceso, las tablas de símbolos más pequeñas reducen directamente el tamaño de la partición del sistema y el espacio en memoria del entorno de ejecución.
  • Optimizaciones más profundas de todo el programa: Permitir que R8 cambie el nombre de los identificadores desbloquea la combinación de clases, el aplanamiento de paquetes y la anulación de duplicación de clases sintéticas en los paquetes que, de lo contrario, R8 debe omitir para evitar colisiones de nombres.
  • Simbolización de seguimiento de pila completa: Habilitar la ofuscación en Soong no reduce la capacidad de depuración. Para cada destino compilado con R8, Soong emite un archivo de asignación proguard_dictionary en el directorio intermedio del módulo, agrupa todos los diccionarios de módulos en el artefacto proguard-dict.zip de la compilación, incorpora un hash de asignación (--map-id-template) en el encabezado DEX y reescribe los atributos de la clase SourceFile (--source-file-template) para que retrace pueda simbolizar los seguimientos de pila automáticamente.

Mantén obfuscate: false (o conserva de forma explícita los nombres de las APIs públicas) solo para lo siguiente:

  • Bibliotecas compartidas en bootclasspath o classpath de system_server: Los módulos (java_library o java_sdk_library) que exponen una superficie de API externa vinculada de forma dinámica por otros módulos en el entorno de ejecución (como los destinos framework.jar, services.jar o <uses-library>) deben mantener obfuscate: false o conservar de forma explícita su superficie de API con las reglas protect_api_surface: true, @KeepForApi o -keep para que los emisores compilados con sus stubs puedan resolver los nombres de clases y miembros en el entorno de ejecución. En los módulos java_sdk_library, la configuración protect_api_surface: true genera y adjunta automáticamente reglas de conservación de ProGuard de Metalava en todos los permisos de API habilitados (público, sistema, module-lib, system-server y prueba) a la biblioteca de implementación. Cuando se habilita la optimización generalizada de R8 en destinos de bootclasspath no actualizables (framework-minus-apex, ext y framework-location), también incluyen framework-hiddenapi-proguard-rules (generado por hiddenapi_to_proguard) para retener los miembros que se indican en los archivos boot/hiddenapi/*.txt activos (se omite hiddenapi-max-target-o.txt).

Antes de habilitar obfuscate: true en una aplicación, verifica los siguientes requisitos previos de migración:

  • Dependencias externas del APK de prueba: Si un APK de android_test externo establece instrumentation_for: "MyApp" y, además, invoca directamente clases o métodos internos de MyApp, cambiar el nombre de esos símbolos provoca un error de NoSuchMethodError o NoClassDefFoundError en el tiempo de ejecución de la prueba. Configura trace_references_from (o allow_traced_references con trace_references: true), estructura la prueba como un APK de instrumentación automática que vincule MyApp.impl de forma estática o anota hooks de prueba con @VisibleForTesting (consulta el Paso 2: Migra las reglas de conservación basadas en pruebas) para que se conserven los símbolos internos que necesitan las pruebas mientras se ofusca el resto de la app.
  • Reflexión basada en cadenas y JNI: Si un módulo busca clases, métodos o campos por nombres de cadenas literales (Class.forName, getDeclaredMethod o JNI FindClass y GetMethodID) sin reglas de conservación ni anotaciones, la ofuscación cambia el nombre de esos destinos y rompe las búsquedas de tiempo de ejecución. (Ten en cuenta que la reflexión sin anotaciones también falla en shrink: true y optimize: true). Anota esos puntos de entrada con la Guía para conservar anotaciones (@UsesReflection, @UsedByReflection o @UsedByNative) para que R8 conserve sus nombres y ofusque el resto del módulo.

Sigue el principio de cero marcas personalizadas

El módulo ideal de la plataforma de Android no tiene archivos proguard.flags ni keep.xml personalizados. En la compilación de la plataforma de Android, la gran mayoría de las reglas de conservación personalizadas son redundantes:

  • Líneas de base de la plataforma y AAPT2: La mayoría de los puntos de entrada ya se conservan automáticamente con las líneas de base de la plataforma global (como @Keep, métodos JNI native y @VisibleForTesting) o se generan con AAPT2 a partir de AndroidManifest.xml y recursos de diseño (consulta Información sobre las reglas de conservación predeterminadas de la plataforma).
  • Alternativas orientadas: Cuando se deban conservar los puntos de entrada, prefiere las anotaciones del sitio de declaración (keepanno, @VisibleForTesting), las reglas de biblioteca exportadas (export_proguard_flags_files: true) o la vinculación de pruebas estáticas por sobre los archivos .flags separados (consulta Cómo auditar y migrar las reglas de conservación existentes).

Antes de agregar o conservar un archivo proguard.flags o keep.xml personalizado, verifica si la regla ya se controla con las líneas de base predeterminadas o si se puede migrar a anotaciones de código.

Comprende las reglas de conservación predeterminadas de la plataforma

Soong pasa automáticamente las siguientes reglas de referencia globales a cada invocación de R8 en los destinos de compilación de DEX (android_app, android_test y módulos java_library instalables), configurados en build/soong/java/dex.go:

  • build/make/core/proguard.flags: Conserva las clases y los miembros anotados con @com.android.internal.annotations.VisibleForTesting en todos los paquetes, y con @VisibleForTesting (androidx.annotation.VisibleForTesting o com.google.common.annotations.VisibleForTesting) dentro de los paquetes android.**, com.android.** y com.google.android.**. También conserva @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback y @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags: Conserva los atributos SourceFile para los seguimientos de pila, las anotaciones de visibilidad del entorno de ejecución (RuntimeVisible*Annotations), los atributos Exceptions y AnnotationDefault, los métodos native, los miembros Serializable, los métodos @JavascriptInterface, los constructores Throwable(String), los campos Parcelable$CREATOR y los campos protobuf MessageLite.
  • build/make/core/proguard/kotlin.flags: Silencia las advertencias inofensivas para meta-anotaciones específicas de Kotlin (kotlin.Metadata y kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) y quita las anotaciones de Kotlin DebugMetadata en las compilaciones de lanzamiento.
  • build/make/core/proguard/checknotnull.flags: Reemplaza las llamadas comunes de ayuda para la verificación de valores nulos (com.google.common.base.Preconditions.checkNotNull y dagger.internal.Preconditions.checkNotNull*) por verificaciones de valores nulos de código de bytes concisas. Este archivo omite Objects.requireNonNull de forma intencional para conservar los mensajes de excepción explícitos en los límites de la API del framework.
  • build/make/core/proguard/enumvalues.flags: Conserva los métodos values y valueOf en los tipos enum, a menos que la configuración de compilación los inhabilite.
  • Reglas generadas automáticamente por AAPT2: AAPT2 inspecciona los archivos AndroidManifest.xml, XML de diseño y XML de preferencias combinados para generar reglas de conservación exactas para cada Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, subclase View personalizada, subclase Preference y método android:onClick XML registrados.

Audita y migra las reglas de conservación existentes

Cuando audites archivos proguard.flags o keep.xml existentes en un repositorio de la plataforma, evalúa cada regla en orden según la siguiente jerarquía de cuatro pasos:

Paso 1: Borra las reglas redundantes o inactivas

Borra las reglas que ya están cubiertas por las líneas de base globales, las reglas de manifiesto y diseño de AAPT2, o los valores predeterminados de R8, así como las reglas que hacen referencia a clases o paquetes que ya no existen.

Si todas las reglas de un archivo .flags son redundantes, borra el archivo y quita proguard_flags_files de Android.bp. Si el bloque optimize restante solo reitera la configuración predeterminada, quita el bloque redundante y formatea el archivo de compilación con bpfmt -w Android.bp. Cuando limpies un paquete, verifica si los módulos complementarios de los subdirectorios (como las variantes de destino de Kotlin) hacen referencia al mismo archivo de marcas y actualízalos juntos.

Paso 2: Migra las reglas de conservación basadas en pruebas

Mueve las reglas de conservación basadas en pruebas fuera de los archivos .flags de producción personalizados con uno de los siguientes patrones:

  • Seguimiento de referencias de destinos de prueba o complementarios (trace_references_from o allow_traced_references): Cuando el destino y un módulo dependiente directo compilan su propio DEX (android_app, android_test o un java_library dexed) y están conectados a través de instrumentation_for o libs, Soong puede ejecutar R8 tracereferences en el módulo dependiente y alimentar las reglas de conservación generadas en la compilación de R8 del destino sin crear un ciclo de dependencia ni requerir una división manual de la biblioteca .impl:

    • Pruebas en el mismo repositorio (trace_references_from): Enumera los dependientes directos de instrumentation_for o libs en el módulo de destino (esto también se propaga automáticamente en las variantes de override_android_app y override_android_test, y sigue admitiendo bibliotecas de Java complementarias no dependientes cuando se vinculan con una división de .impl):

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              obfuscate: true,
              shrink_resources: true,
              trace_references_from: ["MyAppTests"],
          },
      }
      
      android_test {
          name: "MyAppTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
      }
      
    • Pruebas de varios repositorios o de proveedores (allow_traced_references y trace_references): Cuando el módulo de destino no puede enumerar el dependiente directamente en trace_references_from (como las pruebas en otro repositorio o árbol de proveedores), configura allow_traced_references: true en el módulo de destino y habilita cada dependiente con trace_references: true:

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              allow_traced_references: true,
          },
      }
      
      android_test {
          name: "MyAppVendorTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
          optimize: {
              trace_references: true,
          },
      }
      
    • Reflexión basada en cadenas en las pruebas: tracereferences inspecciona las referencias del grupo de constantes de código de bytes y no puede detectar la reflexión basada en cadenas en las pruebas (Class.forName, getDeclaredField, getDeclaredMethod o asistentes de inyección de campo reflexivos). Anota los campos y métodos de producción a los que se accede de forma reflexiva con @VisibleForTesting (o @UsedByReflection).

  • Vincula la biblioteca de implementación a las pruebas (static_libs): Si quieres que no se retengan puntos de entrada exclusivos para pruebas en el APK de producción y no quieres que los cambios en las pruebas vuelvan a activar R8 en el destino de producción, coloca los archivos de origen de la app en un android_library (MyApp.impl) y vincula de forma estática MyApp.impl a MyApp y MyAppTests:

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    Este patrón de 3 módulos permite que MyAppTests se ejecute como una prueba de autoinstrumentación con acceso completo a las clases internas, mientras que permite que MyApp se reduzca y se ofusque solo en los puntos de entrada de producción.

  • Anota hooks de prueba con @VisibleForTesting: Si un APK de prueba externo llama solo a unos pocos métodos o constructores internos en un destino android_app, puedes anotar esas declaraciones con @VisibleForTesting. El modelo de referencia global de build/make/core/proguard.flags conserva automáticamente los elementos de @VisibleForTesting en los paquetes android.**, com.android.** y com.google.android.** sin archivos .flags personalizados. (Para los módulos de proveedores fuera de estos espacios de nombres, usa trace_references_from, @UsedByReflection o reglas de biblioteca exportadas). Cuando anotes manualmente con @VisibleForTesting en lugar de usar trace_references_from o trace_references, también anota las clases que se pasaron a Mockito mock(...) o spy(...) (no solo los métodos con stub), ya que mockito-target (dexmaker-mockito) integrado en el dispositivo genera subclases de tiempo de ejecución y R8 puede marcar clases sin subclases final (MockitoException: Can't mock/spy class ... - final class).

  • Evita o vuelve a empaquetar las bibliotecas estáticas superpuestas (jarjar_rules): Cuando un android_app y su APK de prueba instrumentation_for vinculan de forma estática la misma biblioteca (directamente, como guava, o de forma transitiva), tracereferences ve esas clases definidas en la prueba y no emite reglas de conservación para la app, y, en el tiempo de ejecución, el cargador de clases superior de la app oculta la copia de la prueba con sus clases reducidas de R8, lo que causa IllegalAccessError o NoSuchMethodError. Si la prueba no necesita su propia copia empaquetada, quita el duplicado de la static_libs de la prueba (o vincúlalo a través de libs) para evitar la colisión; de lo contrario, vuelve a empaquetar los paquetes de biblioteca estática superpuestos de la app con un prefijo específico del módulo mediante jarjar_rules.

Paso 3: Exporta las reglas de la biblioteca propietaria

Si un java_library o android_library compartido requiere reglas de conservación para su propia reflexión interna o devoluciones de llamada de JNI, define las reglas en el destino de la biblioteca y establece export_proguard_flags_files: true:

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

Todos los destinos de biblioteca y android_app downstream que vinculan de forma estática my-shared-library heredan esas reglas automáticamente, por lo que las apps downstream no necesitan duplicarlas. Cuando varios módulos de diferentes directorios deben compartir un conjunto de reglas que no está vinculado a una sola biblioteca de código, publica el archivo .flags a través de un filegroup explícito en Android.bp para que los módulos puedan hacer referencia a :my-shared-flags de forma clara en los límites del paquete. Si deseas obtener orientación general sobre cómo crear reglas de conservación para consumidores de bibliotecas sin restringir la optimización de apps downstream, consulta Optimización para autores de bibliotecas.

Paso 4: Anota las declaraciones en el código fuente

Cuando se accede a una clase, un método, un constructor o un campo a través de la reflexión o la JNI, y AAPT2 o las líneas de base globales no los abarcan, reemplaza las reglas -keep separadas en los archivos .flags por anotaciones de origen de la Guía para conservar anotaciones (consulta la referencia de Javadoc de keepanno):

  • @UsesReflection: Se prefiere esta anotación cuando controlas el código que realiza la reflexión. Colócala en el sitio de llamada de reflexión para declarar a qué clases, métodos o campos de destino se accede de forma dinámica. Debido a que @UsesReflection codifica automáticamente una condición previa de que el sitio de llamada anotado es accesible, R8 elimina tanto el emisor como el destino reflexivo si no se usa el sitio de llamada.
  • @UsedByReflection: Se coloca en clases, métodos, campos o constructores que se instancian o invocan de forma reflexiva mediante código o bibliotecas externos, como clases cargadas con Class.forName de claves Bundle o Settings, o clases de complementos cargadas en cargadores de clases dinámicos. Especifica kind (como KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) y restricciones de parámetros para que R8 conserve solo el contrato reflexivo exacto y pueda optimizar o quitar miembros de la clase que no se usen. No agregues @UsedByReflection a los componentes registrados en el manifiesto, como JobService o BroadcastReceiver, ya que AAPT2 ya los conserva.
  • @UsedByNative: Se coloca en métodos o campos a los que se accede desde el código JNI de C o C++ con GetMethodID, GetStaticMethodID o GetFieldID.
  • @KeepForApi: Se coloca en clases o miembros de la API de la biblioteca que deben permanecer intactos cuando se reduce una biblioteca antes de la distribución.
  • @Keep (androidx.annotation.Keep): Se usa como resguardo cuando no se aplica keepanno. Aplicar @Keep a una clase conserva la clase y todos sus miembros de forma incondicional. Aplicar @Keep a un método o campo actúa como un punto de entrada incondicional que conserva el miembro y la clase que lo contiene, incluso si nunca se crea una instancia de la clase, mientras que keepanno expresa la accesibilidad condicional.

Para usar anotaciones de R8 keepanno en un módulo de Soong, haz lo siguiente:

  1. Agrega "keepanno-annotations" a libs en Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. Importa com.android.tools.r8.keepanno.annotations.* y anota la declaración o el sitio de llamada en el código fuente de Java o Kotlin:

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8 traduce las anotaciones keepanno directamente en su modelo de regla de conservación interno y quita las anotaciones del resultado final de DEX, lo que agrega cero sobrecarga de código de bytes en el entorno de ejecución.

Evita errores comunes

En las siguientes secciones, se describen las dificultades frecuentes de proguard.flags y keep.xml en el código de la plataforma y cómo corregirlas.

Reglas de conservación de componentes generales

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Por qué afecta el rendimiento: Esta regla es completamente redundante con AAPT2. Como usa un comodín sin verificar si el componente está registrado en el AndroidManifest.xml combinado final, obliga a R8 a retener cada subclase Activity, Service o BroadcastReceiver que se encuentre en cualquier parte de la ruta de clase, incluidos los componentes de biblioteca sin usar y las actividades de depuración inhabilitadas.
  • Corrección recomendada: Borra la regla. AAPT2 inspecciona el manifiesto combinado y genera reglas de -keep exactas para los componentes registrados.

Reglas de conservación de controladores de clics de XML generales

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Por qué afecta el rendimiento: Conservar cada método public void *(View) en cada clase del módulo impide que R8 quite o intercale cualquier método que acepte un solo parámetro View.
  • Corrección recomendada: Borra la regla. AAPT2 analiza los archivos en formato XML de diseño y genera reglas de conservación orientadas para los métodos a los que hacen referencia los atributos android:onClick.

Reglas de conservación de métodos get y set de View generales

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • Por qué afecta el rendimiento: Esta regla obliga a R8 a retener cada método get y set en cada subclase View de la app y todas las bibliotecas vinculadas (incluidas las bibliotecas de AndroidX y Material), lo que bloquea la eliminación de métodos inactivos y la inserción en el código de la IU.
  • Corrección recomendada: Borra la regla. AAPT2 ya conserva los constructores para las clases View personalizadas que se aumentan a partir de archivos en formato XML de diseño. Si el código anima una propiedad de vista con nombres de cadena reflexivos, como ObjectAnimator.ofFloat(view, "translationZ", ...), reemplaza el nombre de la cadena por una referencia de propiedad con tipo (View.TRANSLATION_Z o una implementación personalizada de FloatProperty o IntProperty) para evitar la reflexión por completo. Si no se puede evitar el acceso reflexivo a las propiedades, anota el método get o set específico con @UsedByReflection.

Inhabilitación de la optimización o la ofuscación generales

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Por qué afecta el rendimiento: Colocar -dontoptimize o -dontshrink dentro de un archivo .flags anula de forma silenciosa la configuración de Android.bp del módulo y, además, inhabilita los pases de optimización en todo el destino. Peor aún, si una biblioteca exporta un archivo .flags que contiene -dontoptimize, inhabilita la optimización de R8 para cada android_app descendente que vincule la biblioteca.
  • Corrección recomendada: Quita -dontoptimize, -dontshrink y -dontobfuscate de los archivos .flags. Controla el comportamiento de optimización de forma explícita en Android.bp con el bloque optimize (shrink, optimize, obfuscate) en el destino de hoja.

Marcas de diagnóstico en reglas confirmadas

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Por qué perjudica la compilación: Las marcas de diagnóstico inundan los registros de compilación o intentan escribir archivos de salida en rutas de acceso locales durante las compilaciones de Soong en la zona de pruebas.
  • Corrección recomendada: Borra estas marcas de los archivos .flags confirmados. Soong escribe automáticamente las asignaciones de R8 y los resultados de uso, como proguard_dictionary y proguard_usage.zip, en el directorio intermedio del módulo en out/soong/.intermediates/.

Reglas de conservación de producción para el código de prueba

  • Por qué afecta el rendimiento: Agregar reglas de -keep personalizadas solo para el acceso de prueba obliga a que ese código permanezca sin ofuscar y se conserve en las compilaciones de producción. Si bien configurar trace_references_from (o allow_traced_references con trace_references: true) o anotar métodos con @VisibleForTesting evita mantener reglas de -keep manuales y conserva solo lo que las pruebas referencian, esos símbolos se siguen enviando en el objeto binario de producción.
  • Corrección recomendada: Elimina las reglas de -keep de prueba personalizadas habilitando trace_references_from (o allow_traced_references y trace_references: true), o bien estructura la app en una biblioteca de .impl vinculada a un APK de prueba de instrumentación autónoma con static_libs para mantener los puntos de entrada solo de prueba completamente fuera del APK de producción, como se describe en el Paso 2: Migra las reglas de conservación basadas en pruebas.

Comodines de recursos generales en keep.xml

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • Por qué afecta el rendimiento: Los comodines generales retienen todos los recursos del tipo coincidente en el módulo y sus dependencias, lo que anula la reducción de recursos (shrink_resources: true) y aumenta el tamaño del APK y la tabla resources.arsc asignada.
  • Corrección recomendada:
    • Prefiere las referencias de recursos estáticos antes que keep.xml: Evita usar el método Resources.getIdentifier para buscar un conjunto limitado de recursos de forma dinámica, como cadenas de experimentos numeradas o elementos de diseño temáticos. En su lugar, usa una sentencia switch de tiempo de compilación o asigna constantes estáticas de R.id, R.string o R.drawable. Las referencias estáticas permiten que R8 y AAPT2 rastreen los recursos activos exactos, eliminen la sobrecarga de búsqueda de cadenas en el entorno de ejecución y quiten la necesidad de keep.xml por completo.
    • Enumera nombres de recursos específicos: Si se requiere una búsqueda dinámica, como por un lector de licencias de terceros, enumera los identificadores de recursos exactos en tools:keep (por ejemplo, tools:keep="@raw/third_party_licenses").
    • Borra los archivos keep.xml redundantes: Si ya se hace referencia a los recursos de la lista de forma estática en el código (R.raw.foo) o en XML (@raw/foo), el reductor los conserva automáticamente, por lo que puedes borrar res/raw/keep.xml.

Verifica y audita los cambios en las reglas

Cada vez que quites o restrinjas las reglas de conservación en proguard.flags o keep.xml, verifica que el módulo se compile correctamente, apruebe las pruebas de unidades y de instrumentación, y conserve todos los puntos de entrada requeridos.

Compila y prueba módulos

  1. Compila el módulo de forma limpia para verificar que R8 y AAPT2 se completen sin advertencias de referencias faltantes:

    m <MODULE_NAME>
    
  2. Ejecuta pruebas de unidades y de instrumentación para el módulo con atest:

    atest <TEST_MODULE_NAME>
    
  3. En el caso de las apps del sistema, los servicios con privilegios o los módulos que dependen del hardware, ejecuta pruebas de instrumentación y de IU en los dispositivos físicos de destino o en el lab de pruebas de tu dispositivo para ejercitar las rutas de reflexión del entorno de ejecución, las vinculaciones de IPC y la inflación de recursos.

Inspecciona las diferencias de DEX y recursos

Compara el APK o JAR compilado antes y después de los cambios en las reglas para confirmar que R8 quite el código y los recursos inactivos sin quitar los puntos de entrada esperados:

  • Usa apkanalyzer o dexdump para inspeccionar las clases, los métodos y los campos retenidos en los archivos APK o DEX de salida:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Cuando modifiques keep.xml o habilites shrink_resources, usa aapt2 dump resources para verificar que la compilación quite los recursos sin usar y conserve los necesarios:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Cuando ejecutes prebuilts/r8/r8.jar directamente en el classes.jar previo a R8 de un módulo para verificar los cambios de marcas sin conexión sin una compilación de plataforma completa, pasa los JAR de ruta de clase --lib del módulo (android.jar, framework.jar y libs compartido) y las reglas de proguard.options generadas por AAPT2 desde AndroidManifest.xml y los XML de diseño. Sin esas entradas, R8 no puede resolver los supertipos del framework (Activity, Service, Phone, Handler) y retiene de forma insuficiente los métodos de jerarquía.

Analiza el radio de conservación y la subsunción de reglas con R8

El compilador de código abierto R8 incluye un analizador de Keep Radius (también expuesto en Android Studio y Gradle como el analizador de configuración de R8) que mide el impacto exacto de cada regla de conservación durante la compilación. Soong integra este analizador directamente en la compilación de la plataforma de Android (configurada en build/soong/java/dex.go).

Para analizar las reglas de conservación de un módulo de plataforma o de toda una compilación, haz lo siguiente:

  1. Ejecuta la compilación con la variable de entorno R8_DUMP_KEEP_RADIUS=true:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    Cuando se establece R8_DUMP_KEEP_RADIUS=true, Soong le indica a R8 que registre las métricas de las reglas de conservación en un archivo r8keepradius.pb intermedio para cada módulo compilado en out/soong/.intermediates/. Para cada regla de conservación y anotación keepanno, R8 registra lo siguiente:

    • Radio de conservación inmediato: Son las clases, los campos y los métodos exactos que conserva la regla, junto con las restricciones específicas que aplica contra la reducción, la optimización o la ofuscación.
    • Subsunción de reglas: Qué otras reglas o anotaciones de conservación ya retienen los mismos elementos. Si una regla personalizada está completamente incluida en una regla de AAPT2 o una regla de referencia global, puedes borrarla de forma segura.
    • Reglas globales y para todo el paquete: Son reglas que usan comodines amplios para todo el paquete o que aplican directivas de configuración globales.
  2. Convierte el resultado de r8keepradius.pb en un informe HTML interactivo con KeepRadiusHtmlReportGenerator (incluido en prebuilts/r8/r8.jar):

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    Cuando se proporciona un directorio, KeepRadiusHtmlReportGenerator recorre todos los archivos *keepradius*.pb, genera un informe HTML para cada módulo y crea out/keep_radius_reports/keepradius.html que resume los recuentos de elementos activos, los recuentos de elementos conservados y las reglas de conservación de mayor radio en toda la compilación. Para obtener más información sobre cómo interpretar las puntuaciones de reducción, optimización y ofuscación en el informe generado, consulta Cómo usar el Analizador de configuración de R8.

Soong también admite variables de entorno adicionales de diagnóstico de R8 y de bucle interno en build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: Escribe r8inputs.zip en el directorio intermedio del módulo que contiene todos los JAR de entrada, los JAR de biblioteca y las configuraciones de ProGuard combinadas para la reproducción independiente de R8.
  • R8_DUMP_PERFETTO_TRACE=true: Escribe r8trace.ptrace en el directorio intermedio del módulo para inspeccionar los pases de compilación de R8 en Perfetto.
  • SOONG_SKIP_TRACE_REFERENCES=true: Omite la ejecución de las reglas tracereferences configuradas por trace_references_from o trace_references para acelerar las compilaciones de bucle interno locales cuando se itera en el código de prueba.

Valida las reglas de consumidor de la biblioteca

Cuando un java_library o android_library exporta reglas de conservación a consumidores downstream (export_proguard_flags_files: true), esas reglas no deben incluir marcas globales que alteren o inhabiliten la optimización para la app consumidora.

R8 proporciona un analizador de reglas de conservación de código abierto (ProcessKeepRules), que se expone en la compilación de la plataforma de Android como la herramienta de host process-keep-rules (definida en prebuilts/r8/Android.bp):

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

La herramienta process-keep-rules analiza cada archivo de configuración y falla con diagnósticos de archivos y líneas si encuentra directivas que no están permitidas en las reglas del consumidor de la biblioteca. Entre las directivas no permitidas, se incluyen la inhabilitación de la optimización global, la reducción o la ofuscación (como -dontoptimize o -dontshrink), el reempaquetado de paquetes y las marcas de modificación de acceso, las marcas de diagnóstico o asignación, y -keepattributes a nivel de la app.

Audita árboles de fuentes con pgaudit.py

Para auditar los directorios de código fuente no compilados junto con los analizadores de tiempo de compilación de R8, el árbol de la plataforma de Android incluye la secuencia de comandos pgaudit.py en build/make/core/proguard/tools/. Esta herramienta de análisis estático analiza los archivos Android.bp, proguard.flags y keep.xml en un repositorio para marcar las reglas que ya están cubiertas por AAPT2 o los valores de referencia de la plataforma global, los comodines amplios, las anulaciones de -dontoptimize y las reglas de conservación solo para pruebas:

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

Los equipos de plataformas y OEM pueden combinar pgaudit.py para una clasificación rápida del árbol de fuentes, process-keep-rules para validar las reglas de bibliotecas exportadas y R8_DUMP_KEEP_RADIUS=true con KeepRadiusHtmlReportGenerator para medir el radio de retención exacto de clases, campos y métodos de cada regla en una compilación.