Ottimizzare il codice e le risorse della piattaforma con R8

Il sistema di build della piattaforma Android (Soong) esegue il compilatore R8 su target che compilano il bytecode DEX, tra cui app per Android (android_app), test (android_test) e librerie Java installabili con compile_dex: true (ad esempio services.jar), per ridurre, ottimizzare e rimuovere codice e risorse inutilizzati. Nelle librerie statiche (android_library e java_library statico), Soong non esegue R8 direttamente, ma utilizza il blocco di proprietà optimize per collegare e propagare le regole di conservazione dei consumer (export_proguard_flags_files: true) ai target DEX downstream che le collegano staticamente.

Per i platform engineer che creano immagini di sistema o pacchetti di fornitori, l'eliminazione di regole di conservazione eccessivamente ampie offre vantaggi diretti per l'integrità del sistema:

  • Impatto ridotto della partizione di sistema:R8 rimuove classi, metodi e risorse non utilizzati prima di pacchettizzare APK e JAR in /system, /system_ext, /product e /vendor.
  • Artefatti compilati più piccoli:un numero inferiore di metodi DEX significa file .odex e .vdex più piccoli generati da dex2oat durante la compilazione in fase di build o sul dispositivo.
  • Minore pressione sulla memoria di runtime:poiché Android mappa .odex, .vdex e le tabelle delle risorse APK nella memoria di processo utilizzando chiamate mmap con paginazione on demand, i binari più piccoli riducono gli errori di pagina principali durante l'avvio dell'app e riducono la memoria del codice e delle risorse residenti nei processi. Per saperne di più sull'impatto del codice dell'app compilato sulla memoria del dispositivo, consulta Il codice dell'app è memoria.
  • Ottimizzazione più efficace dell'intero programma:i vincoli di conservazione ristretti consentono a R8 di incorporare i metodi, devirtualizzare le chiamate virtuali e di interfaccia, eliminare i campi inutilizzati e propagare le costanti tra le classi.

Configura l'ottimizzazione R8 in Soong

Nei file Android.bp, configura l'ottimizzazione R8 utilizzando il blocco della proprietà optimize sui target android_app e java_library installabili (o sui moduli android_library e java_library statici per esportare le regole di conservazione dei consumer):

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

La tabella seguente riassume le proprietà optimize più comuni in Soong (definite in build/soong/java/dex.go):

Proprietà Descrizione
enabled Controlla se R8 viene eseguito sulla destinazione. Il valore predefinito è true per tutti i target android_app.
shrink Controlla il tree shaking per rimuovere classi, campi e metodi non raggiungibili. Il valore predefinito è true per i target android_app (false per i moduli java_library e di test autonomi, che passano -dontshrink se non impostato esplicitamente).
optimize Controlla le ottimizzazioni del bytecode, come l'inlining dei metodi, l'unione delle classi, la propagazione delle costanti e la rimozione dei rami inattivi. Il valore predefinito è true per i target android_app (controllati dal flag di build di release RELEASE_R8_OPTIMIZE_BY_DEFAULT).
obfuscate Controlla la minimizzazione e la ridenominazione degli identificatori. Il valore predefinito è false per la compatibilità storica, ma impostalo su true ove possibile per ridurre le dimensioni del DEX e sbloccare ottimizzazioni più approfondite (consulta Attiva l'offuscamento ove possibile).
shrink_resources Rimuove le risorse inutilizzate (res/ voci) dall'APK pacchettizzato dopo la riduzione del codice. Il valore predefinito è false.
optimized_shrink_resources Esegue la pipeline integrata di riduzione del codice e delle risorse R8 in modo che R8 tracci congiuntamente i riferimenti al codice e alle risorse in un unico passaggio. Se shrink_resources: true è impostato, il valore predefinito è RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true nelle build della piattaforma standard).
proguard_flags_files Elenca i file .flags o .pro specifici del modulo contenenti regole di conservazione personalizzate. Evita di aggiungere file personalizzati quando sono sufficienti le annotazioni o i valori predefiniti standard.
export_proguard_flags_files Propaga il proguard_flags_files di questa libreria ai moduli downstream che dipendono da questa in modo statico.
trace_references_from Elenca i target dipendenti diretti dalla compilazione DEX (connessi tramite instrumentation_for o libs) i cui riferimenti di bytecode in questo target R8 vengono tracciati e conservati automaticamente (anche propagati tra le varianti override_android_app e override_android_test e supportati per le librerie Java companion non dipendenti se accoppiati con una suddivisione .impl).
allow_traced_references Consente ai target dipendenti dalla compilazione DEX diretta (connessi tramite instrumentation_for o libs, ad esempio test tra repository o di fornitori che non possono essere elencati in trace_references_from) di attivare il tracciamento dei riferimenti al bytecode in questo target impostando trace_references: true.
trace_references Se impostato su true in un dipendente di compilazione DEX diretto di una destinazione con allow_traced_references: true, esegue R8 tracereferences su questo modulo e passa le regole di conservazione generate al modulo di destinazione.
protect_api_surface Passa --protect-api-surface a R8. Se impostato su un java_sdk_library, genera e collega automaticamente anche le regole di conservazione dell'API Metalava in tutti gli ambiti API abilitati (pubblico, di sistema, module-lib, system-server e di test) alla libreria di implementazione.

Anche le librerie statiche (android_library, java_library e importazioni AAR) possono impostare required_downstream_optimization: { enabled: true, optimize: true } in Android.bp per richiedere che qualsiasi target di compilazione DEX downstream che le colleghi staticamente (direttamente o in modo transitivo) attivi la riduzione R8 e l'ottimizzazione del bytecode. Soong attiva questa funzionalità automaticamente su androidx.compose.runtime_runtime in modo che i target che collegano Jetpack Compose siano ottimizzati con R8.

Attiva l'offuscamento, ove possibile

In Soong, obfuscate viene impostato su false per impostazione predefinita per la compatibilità con le versioni precedenti, ma devi impostare esplicitamente obfuscate: true su android_app e sui target DEX autonomi, ove possibile:

  • DEX e .vdex più piccoli: la ridenominazione di pacchetti, classi, campi e metodi in identificatori brevi (a, b) riduce il pool di stringhe DEX (string_ids e string_data_item) e i descrittori dei tipi. Poiché Android mappa i file .vdex e .odex nella memoria di processo, le tabelle dei simboli più piccole riducono direttamente sia le dimensioni della partizione di sistema sia il footprint della memoria di runtime.
  • Ottimizzazioni più approfondite dell'intero programma:consentire a R8 di rinominare gli identificatori sblocca l'unione delle classi, l'appiattimento dei pacchetti e la deduplicazione delle classi sintetiche tra i pacchetti che altrimenti R8 deve saltare per evitare conflitti di nomi.
  • Simbolizzazione completa dell'analisi dello stack:l'attivazione dell'offuscamento in Soong non riduce la possibilità di eseguire il debug. Per ogni target compilato con R8, Soong genera un file di mappatura proguard_dictionary nella directory intermedia del modulo, raggruppa tutti i dizionari del modulo nell'artefatto proguard-dict.zip della build, incorpora un hash di mappatura (--map-id-template) nell'intestazione DEX e riscrive gli attributi SourceFile della classe (--source-file-template) in modo che retrace possa simbolizzare automaticamente le analisi dello stack.

Mantieni obfuscate: false (o conserva esplicitamente i nomi delle API pubbliche) solo per:

  • Librerie condivise nel bootclasspath o nel classpath system_server: i moduli (java_library o java_sdk_library) che espongono una superficie API esterna collegata dinamicamente da altri moduli in fase di runtime (come i target framework.jar, services.jar o <uses-library>) devono mantenere obfuscate: false o preservare esplicitamente la loro superficie API utilizzando le regole protect_api_surface: true, @KeepForApi o -keep in modo che i chiamanti compilati in base ai loro stub possano risolvere i nomi di classi e membri in fase di runtime. Nei moduli java_sdk_library, l'impostazione protect_api_surface: true genera e collega automaticamente le regole di conservazione di Metalava ProGuard in tutti gli ambiti API abilitati (pubblico, di sistema, module-lib, system-server e di test) alla libreria di implementazione. Quando l'ottimizzazione R8 generalizzata è abilitata su target bootclasspath non aggiornabili (framework-minus-apex, ext e framework-location), questi includono anche framework-hiddenapi-proguard-rules (generato da hiddenapi_to_proguard) per conservare i membri elencati nei file boot/hiddenapi/*.txt attivi (omettendo hiddenapi-max-target-o.txt).

Prima di abilitare obfuscate: true su un'applicazione, verifica i seguenti prerequisiti di migrazione:

  • Dipendenze APK di test esterno:se un APK android_test esterno imposta instrumentation_for: "MyApp" e richiama direttamente classi o metodi interni di MyApp, la ridenominazione di questi simboli causa NoSuchMethodError o NoClassDefFoundError in fase di esecuzione del test. Configura trace_references_from (o allow_traced_references con trace_references: true), struttura il test come APK di autostrumentazione che si collega staticamente a MyApp.impl o annota gli hook di test con @VisibleForTesting (vedi Passaggio 2: esegui la migrazione delle regole di conservazione basate sui test) in modo che i simboli interni necessari per i test vengano conservati durante l'offuscamento del resto dell'app.
  • Reflection basata su stringhe e JNI: se un modulo cerca classi, metodi o campi in base ai nomi delle stringhe letterali (Class.forName, getDeclaredMethod o JNI FindClass e GetMethodID) senza regole o annotazioni di conservazione, l'offuscamento rinomina i target e interrompe le ricerche di runtime. (Tieni presente che la reflection non annotata non riesce anche in shrink: true e optimize: true). Annota questi entry point con la guida alle annotazioni Keep (@UsesReflection, @UsedByReflection o @UsedByNative) in modo che R8 conservi i loro nomi e offuschi il resto del modulo.

Segui il principio di zero flag personalizzati

Il modulo della piattaforma Android ideale non ha file proguard.flags o keep.xml personalizzati. Nella build della piattaforma Android, la stragrande maggioranza delle regole di conservazione personalizzate è ridondante:

  • Baseline della piattaforma e AAPT2: la maggior parte degli entry point viene già conservata automaticamente dalle baseline della piattaforma globale (come @Keep, metodi JNI native e @VisibleForTesting) o generata da AAPT2 da AndroidManifest.xml e dalle risorse di layout (consulta Informazioni sulle regole di conservazione predefinite della piattaforma).
  • Alternative mirate: se gli entry point devono essere conservati, preferisci le annotazioni del sito di dichiarazione (keepanno, @VisibleForTesting), le regole della libreria esportate (export_proguard_flags_files: true) o il collegamento dei test statici rispetto ai file .flags separati (consulta Controlla ed esegui la migrazione delle regole keep esistenti).

Prima di aggiungere o conservare un file proguard.flags o keep.xml personalizzato, verifica se la regola è già gestita dalle baseline predefinite o se può essere migrata alle annotazioni del codice.

Informazioni sulle regole di conservazione predefinite della piattaforma

Soong passa automaticamente le seguenti regole di base globali a ogni chiamata R8 sui target di compilazione DEX (android_app, android_test e moduli java_library installabili), configurati in build/soong/java/dex.go:

  • build/make/core/proguard.flags: conserva le classi e i membri annotati con @com.android.internal.annotations.VisibleForTesting in tutti i pacchetti e con @VisibleForTesting (androidx.annotation.VisibleForTesting o com.google.common.annotations.VisibleForTesting) nei pacchetti android.**, com.android.** e com.google.android.**. Conserva anche @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback e @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags: conserva gli attributi SourceFile per le analisi dello stack, le annotazioni di visibilità di runtime (RuntimeVisible*Annotations), gli attributi Exceptions e AnnotationDefault, i metodi native, i membri Serializable, i metodi @JavascriptInterface, i costruttori Throwable(String), i campi Parcelable$CREATOR e i campi protobuf MessageLite.
  • build/make/core/proguard/kotlin.flags: silenzia gli avvisi innocui per meta-annotazioni Kotlin specifiche (kotlin.Metadata e kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) e rimuove le annotazioni Kotlin DebugMetadata nelle build di release.
  • build/make/core/proguard/checknotnull.flags: sostituisce le chiamate helper di controllo null comuni (com.google.common.base.Preconditions.checkNotNull e dagger.internal.Preconditions.checkNotNull*) con controlli null di bytecode concisi. Questo file omette intenzionalmente Objects.requireNonNull per preservare i messaggi di eccezione espliciti oltre i limiti dell'API del framework.
  • build/make/core/proguard/enumvalues.flags: conserva i metodi values e valueOf sui tipi enum, a meno che non siano disattivati dalla configurazione di compilazione.
  • Regole generate automaticamente da AAPT2:AAPT2 ispeziona i file AndroidManifest.xml uniti, file XML di layout e file XML di preferenze per generare regole di conservazione esatte per ogni Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, sottoclasse View personalizzata, sottoclasse Preference e metodo android:onClick XML registrati.

Controlla ed esegui la migrazione delle regole di conservazione esistenti

Quando esegui il controllo dei file proguard.flags o keep.xml esistenti in un repository della piattaforma, valuta ogni regola in ordine rispetto alla seguente gerarchia in quattro passaggi:

Passaggio 1: elimina le regole ridondanti o obsolete

Elimina le regole già coperte dalle baseline globali, dalle regole di layout e manifest AAPT2 o dalle impostazioni predefinite di R8, nonché le regole che fanno riferimento a classi o pacchetti che non esistono più.

Se tutte le regole di un file .flags sono ridondanti, elimina il file e rimuovi proguard_flags_files da Android.bp. Se il blocco optimize rimanente si limita a riaffermare le impostazioni predefinite, rimuovi il blocco ridondante e formatta il file di build con bpfmt -w Android.bp. Quando esegui la pulizia di un pacchetto, controlla se i moduli complementari nelle sottodirectory (come le varianti di destinazione Kotlin) fanno riferimento allo stesso file di flag e aggiornali insieme.

Passaggio 2: esegui la migrazione delle regole di conservazione basate su test

Sposta le regole di conservazione basate su test dai file .flags di produzione personalizzati utilizzando uno dei seguenti pattern:

  • Traccia i riferimenti dai target di test o companion (trace_references_from o allow_traced_references): quando sia il target sia un modulo dipendente diretto compilano il proprio DEX (android_app, android_test o un java_library dexed) e sono collegati tramite instrumentation_for o libs, Soong può eseguire R8 tracereferences sul modulo dipendente e inserire le regole di conservazione generate nella compilazione R8 del target senza creare un ciclo di dipendenza o richiedere una suddivisione manuale della libreria .impl:

    • Test nello stesso repository (trace_references_from): elenca i dipendenti diretti instrumentation_for o libs sul modulo di destinazione (questo si propaga automaticamente anche tra le varianti override_android_app e override_android_test e continua a supportare le librerie Java companion non dipendenti se accoppiate a una suddivisione .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"],
      }
      
    • Test tra repository o fornitori (allow_traced_references e trace_references): quando il modulo di destinazione non può elencare il dipendente direttamente in trace_references_from (ad esempio i test in un altro repository o albero del fornitore), imposta allow_traced_references: true sul modulo di destinazione e attiva l'opzione da ogni dipendente 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,
          },
      }
      
    • Reflection basata su stringhe nei test:tracereferences ispeziona i riferimenti al pool di costanti del bytecode e non può rilevare la reflection basata su stringhe nei test (Class.forName, getDeclaredField, getDeclaredMethod o helper di field injection riflessiva). Annota i campi e i metodi di produzione a cui si accede in modo riflessivo con @VisibleForTesting (o @UsedByReflection).

  • Collega la libreria di implementazione ai test (static_libs): se vuoi che nell'APK di produzione non vengano mantenuti entry point solo per i test e non vuoi che le modifiche ai test attivino nuovamente R8 sul target di produzione, inserisci i file di codice sorgente dell'app in un android_library (MyApp.impl) e collega staticamente MyApp.impl sia a MyApp sia a 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"],
    }
    

    Questo pattern a 3 moduli consente a MyAppTests di essere eseguito come test di instrumentazione automatica con accesso completo alle classi interne, consentendo al contempo a MyApp di ridurre e offuscare esclusivamente i punti di accesso di produzione.

  • Annota gli hook di test con @VisibleForTesting: se un APK di test esterno chiama solo alcuni metodi o costruttori interni su un target android_app, puoi annotare queste dichiarazioni con @VisibleForTesting. La baseline globale build/make/core/proguard.flags conserva automaticamente gli elementi @VisibleForTesting nei pacchetti android.**, com.android.** e com.google.android.** senza file .flags personalizzati. (Per i moduli del fornitore esterni a questi spazi dei nomi, utilizza trace_references_from, @UsedByReflection o le regole della libreria esportata.) Quando annoti manualmente con @VisibleForTesting anziché utilizzare trace_references_from o trace_references, annota anche le classi passate a Mockito mock(...) o spy(...) (non solo i metodi con stub), perché mockito-target (dexmaker-mockito) on-device genera sottoclassi di runtime e R8 può contrassegnare le classi senza sottoclassi final (MockitoException: Can't mock/spy class ... - final class).

  • Evita o pacchettizza nuovamente le librerie statiche sovrapposte (jarjar_rules): quando sia un android_app sia il relativo APK di test instrumentation_for collegano staticamente la stessa libreria (direttamente, ad esempio guava, o in modo transitivo), tracereferences vede le classi definite nel test e non emette regole di conservazione per l'app. Inoltre, in fase di runtime, il classloader principale dell'app oscura la copia del test con le classi ridotte di R8, causando IllegalAccessError o NoSuchMethodError. Se il test non richiede una copia in bundle, rimuovi il duplicato dal file static_libs del test (o collegalo tramite libs) per evitare la collisione. In caso contrario, pacchettizza nuovamente i pacchetti di librerie statiche sovrapposti dell'app con un prefisso specifico del modulo utilizzando jarjar_rules.

Passaggio 3: esporta le regole dalla libreria proprietaria

Se un java_library o android_library condiviso richiede regole di conservazione per la propria riflessione interna o i callback JNI, definisci le regole sul target della libreria e imposta 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,
    },
}

Tutti i target android_app e della libreria downstream che si collegano staticamente a my-shared-library ereditano automaticamente queste regole, quindi le app downstream non devono duplicarle. Quando più moduli in directory diverse devono condividere un set di regole non associato a una singola libreria di codice, pubblica il file .flags tramite un filegroup esplicito in Android.bp in modo che i moduli possano fare riferimento a :my-shared-flags in modo pulito oltre i confini del pacchetto. Per indicazioni generali sulla creazione di regole di conservazione per i consumer di librerie senza limitare l'ottimizzazione delle app downstream, consulta Ottimizzazione per gli autori di librerie.

Passaggio 4: annota le dichiarazioni nel codice sorgente

Quando si accede a una classe, un metodo, un costruttore o un campo tramite reflection o JNI e non è coperto da AAPT2 o da baseline globali, sostituisci le regole -keep separate nei file .flags con annotazioni di origine della Guida alle annotazioni Keep (consulta il riferimento Javadoc keepanno):

  • @UsesReflection: preferisci questa annotazione quando controlli il codice che esegue la reflection. Inseriscila nel sito di chiamata della reflection per dichiarare quali classi, metodi o campi di destinazione sono accessibili dinamicamente. Poiché @UsesReflection codifica automaticamente una precondizione che il sito di chiamata annotato stesso sia raggiungibile, R8 elimina sia il chiamante sia la destinazione riflessiva se il sito di chiamata non viene utilizzato.
  • @UsedByReflection: da inserire in classi, metodi, campi o costruttori di cui viene creata un'istanza o che vengono richiamati in modo riflessivo da codice o librerie esterni, ad esempio classi caricate con Class.forName da chiavi Bundle o Settings oppure classi di plug-in caricate tra caricatori di classi dinamici. Specifica kind (ad esempio KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) e i vincoli dei parametri in modo che R8 mantenga solo il contratto di riflessione esatto e possa comunque ottimizzare o eliminare i membri non utilizzati della classe. Non aggiungere @UsedByReflection ai componenti registrati nel manifest come JobService o BroadcastReceiver, perché AAPT2 li conserva già.
  • @UsedByNative: posiziona su metodi o campi a cui si accede dal codice JNI C o C++ utilizzando GetMethodID, GetStaticMethodID o GetFieldID.
  • @KeepForApi: da inserire nelle classi o nei membri dell'API della libreria che devono rimanere invariati quando una libreria stessa viene ridotta prima della distribuzione.
  • @Keep (androidx.annotation.Keep): da utilizzare come opzione di riserva quando keepanno non è applicabile. L'applicazione di @Keep a una classe conserva la classe e tutti i relativi elementi in modo incondizionato. L'applicazione di @Keep a un metodo o a un campo funge da punto di accesso incondizionato che conserva il membro e la classe che lo contiene anche se non viene mai creata un'istanza della classe, mentre keepanno esprime la raggiungibilità condizionale.

Per utilizzare le annotazioni R8 keepanno in un modulo Soong:

  1. Aggiungi "keepanno-annotations" a libs in Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. Importa com.android.tools.r8.keepanno.annotations.* e annota la dichiarazione o il sito di chiamata nel codice sorgente 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 le annotazioni keepanno direttamente nel suo modello di regola di conservazione interna e rimuove le annotazioni dall'output DEX finale, aggiungendo un overhead del bytecode di runtime pari a zero.

Evita gli errori comuni

Le sezioni seguenti descrivono le insidie più frequenti di proguard.flags e keep.xml nel codice della piattaforma e come risolverle.

Regole di conservazione dei componenti generali

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Perché influisce negativamente sulle prestazioni: questa regola è completamente ridondante con AAPT2. Poiché utilizza un carattere jolly senza verificare se il componente è registrato nel file AndroidManifest.xml unito finale, forza R8 a conservare ogni sottoclasse Activity, Service o BroadcastReceiver trovata ovunque nel classpath, inclusi i componenti della libreria inutilizzati e le attività di debug disattivate.
  • Correzione consigliata: elimina la regola. AAPT2 ispeziona il manifest unito e genera regole -keep esatte per i componenti registrati.

Regole di conservazione del gestore dei clic XML generico

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Perché influisce negativamente sulle prestazioni: la conservazione di ogni metodo public void *(View) in ogni classe del modulo impedisce a R8 di rimuovere o inserire in linea qualsiasi metodo che accetta un singolo parametro View.
  • Correzione consigliata: elimina la regola. AAPT2 analizza i file XML di layout e genera regole di conservazione mirate per i metodi a cui fanno riferimento gli attributi android:onClick.

Regole di conservazione dei metodi getter e setter di View generici

# 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*();
}
  • Perché influisce negativamente sulle prestazioni: questa regola forza R8 a conservare ogni getter e setter in ogni sottoclasse View dell'app e in tutte le librerie collegate (incluse le librerie AndroidX e Material), bloccando la rimozione dei metodi non utilizzati e l'incorporamento nel codice dell'interfaccia utente.
  • Correzione consigliata: elimina la regola. AAPT2 conserva già i costruttori per le classi View personalizzate create da file XML di layout. Se il codice anima una proprietà della visualizzazione utilizzando nomi di stringhe riflessive come ObjectAnimator.ofFloat(view, "translationZ", ...), sostituisci il nome della stringa con un riferimento alla proprietà digitato (View.TRANSLATION_Z o un'implementazione personalizzata di FloatProperty o IntProperty) per evitare completamente la riflessione. Se non è possibile evitare l'accesso alle proprietà tramite reflection, annota il getter o il setter specifico con @UsedByReflection.

Ottimizzazione o offuscamento generale disattivati

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Perché influisce negativamente sulle prestazioni: l'inserimento di -dontoptimize o -dontshrink all'interno di un file .flags esegue l'override silenzioso delle impostazioni Android.bp del modulo e disattiva i passaggi di ottimizzazione nell'intero target. Inoltre, se una libreria esporta un file .flags contenente -dontoptimize, disattiva l'ottimizzazione R8 per ogni android_app downstream che si collega alla libreria.
  • Correzione consigliata: rimuovi -dontoptimize, -dontshrink e -dontobfuscate dai file .flags. Controlla il comportamento di ottimizzazione in modo esplicito in Android.bp utilizzando il blocco optimize (shrink, optimize, obfuscate) sul target foglia.

Flag di diagnostica nelle regole impegnate

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Perché danneggia la build: i flag di diagnostica inondano i log di build o tentano di scrivere file di output in percorsi locali durante le build Soong in sandbox.
  • Correzione consigliata: elimina questi flag dai file .flags di cui è stato eseguito il commit. Soong scrive automaticamente gli output di mappatura e utilizzo di R8, come proguard_dictionary e proguard_usage.zip, nella directory intermedia del modulo in out/soong/.intermediates/.

Regole di conservazione in produzione per il codice di test

  • Perché influisce negativamente sulle prestazioni: l'aggiunta di regole -keep personalizzate esclusivamente per l'accesso ai test forza il codice a rimanere non offuscato e conservato nelle build di produzione. Sebbene la configurazione di trace_references_from (o allow_traced_references con trace_references: true) o l'annotazione dei metodi con @VisibleForTesting eviti di mantenere regole -keep manuali e conservi solo ciò a cui fanno riferimento i test, questi simboli vengono comunque inclusi nel file binario di produzione.
  • Correzione consigliata:elimina le regole -keep di test personalizzate attivando trace_references_from (o allow_traced_references e trace_references: true) oppure struttura l'app in una libreria .impl collegata a un APK di test di autoinstrumentazione utilizzando static_libs per mantenere i punti di accesso di solo test completamente fuori dall'APK di produzione, come descritto nel Passaggio 2: esegui la migrazione delle regole di conservazione basate su test.

Caratteri jolly delle risorse onnicomprensivi in keep.xml

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • Perché influisce negativamente sulle prestazioni: i caratteri jolly generali conservano ogni risorsa nel tipo corrispondente nel modulo e nelle sue dipendenze, annullando la riduzione delle risorse (shrink_resources: true) e aumentando le dimensioni dell'APK e della tabella resources.arsc mappata.
  • Correzione consigliata:
    • Preferisci i riferimenti alle risorse statiche rispetto a keep.xml: evita di utilizzare il metodo Resources.getIdentifier per cercare dinamicamente un insieme limitato di risorse, ad esempio stringhe di esperimenti numerate o elementi drawable a tema. Utilizza invece un'istruzione switch in fase di compilazione o esegui la mappatura su costanti statiche R.id, R.string o R.drawable. I riferimenti statici consentono a R8 e AAPT2 di tracciare le risorse attive esatte, eliminare l'overhead di ricerca delle stringhe di runtime e rimuovere completamente la necessità di keep.xml.
    • Elenca nomi di risorse specifici: se è richiesta la ricerca dinamica, ad esempio da un lettore di licenze di terze parti, elenca gli identificatori esatti delle risorse in tools:keep (ad esempio, tools:keep="@raw/third_party_licenses").
    • Elimina i file keep.xml ridondanti:se le risorse elencate sono già referenziate staticamente nel codice (R.raw.foo) o nel file XML (@raw/foo), lo shrinker le conserva automaticamente, quindi puoi eliminare res/raw/keep.xml.

Verifica e controlla le modifiche alle regole

Ogni volta che rimuovi o restringi le regole keep in proguard.flags o keep.xml, verifica che la build del modulo sia corretta, superi i test delle unità e di instrumentazione e mantenga tutti gli entry point richiesti.

Crea e testa i moduli

  1. Crea il modulo in modo pulito per verificare che R8 e AAPT2 vengano completati senza avvisi di riferimento mancanti:

    m <MODULE_NAME>
    
  2. Esegui i test delle unità e di instrumentazione per il modulo utilizzando atest:

    atest <TEST_MODULE_NAME>
    
  3. Per le app di sistema, i servizi con privilegi o i moduli dipendenti dall'hardware, esegui test di strumentazione e della UI sui dispositivi fisici di destinazione o nel tuo lab di test dei dispositivi per esercitare i percorsi di riflessione di runtime, le associazioni IPC e l'inflation delle risorse.

Ispeziona le differenze di DEX e risorse

Confronta l'APK o il JAR compilato prima e dopo le modifiche alle regole per verificare che R8 rimuova il codice e le risorse non utilizzati senza eliminare gli entry point previsti:

  • Utilizza apkanalyzer o dexdump per ispezionare le classi, i metodi e i campi conservati nei file APK o DEX di output:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Quando modifichi keep.xml o attivi shrink_resources, utilizza aapt2 dump resources per verificare che la build rimuova le risorse inutilizzate e conservi quelle richieste:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Quando esegui prebuilts/r8/r8.jar direttamente sul classes.jar pre-R8 di un modulo per verificare le modifiche ai flag offline senza una build completa della piattaforma, passa sia i JAR del classpath --lib del modulo (android.jar, framework.jar e libs condiviso) sia le regole proguard.options generate da AAPT2 da AndroidManifest.xml e dagli XML di layout. Senza questi input, R8 non può risolvere i supertipi del framework (Activity, Service, Phone, Handler) e conserva in modo insufficiente i metodi della gerarchia.

Analizza il raggio di conservazione e la sussunzione delle regole con R8

Il compilatore R8 open source include un analizzatore Keep Radius (esposto anche in Android Studio e Gradle come R8 Configuration Analyzer) che misura l'impatto esatto di ogni regola di conservazione durante la compilazione. Soong integra questo analizzatore direttamente nella build della piattaforma Android (configurata in build/soong/java/dex.go).

Per analizzare le regole di conservazione per un modulo della piattaforma o per un'intera build:

  1. Esegui la build con la variabile di ambiente R8_DUMP_KEEP_RADIUS=true:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    Quando R8_DUMP_KEEP_RADIUS=true è impostato, Soong indica a R8 di registrare le metriche delle regole di conservazione in un file r8keepradius.pb intermedio per ogni modulo compilato in out/soong/.intermediates/. Per ogni regola keep e annotazione keepanno, R8 registra:

    • Raggio di conservazione immediato:le classi, i campi e i metodi esatti conservati dalla regola, insieme ai vincoli specifici che applica per la riduzione, l'ottimizzazione o l'offuscamento.
    • Sussunzione delle regole:quali altre regole di conservazione o annotazioni conservano già gli stessi elementi. Se una regola personalizzata è completamente inclusa in una regola AAPT2 o in una regola di riferimento globale, puoi eliminarla in sicurezza.
    • Regole globali e a livello di pacchetto: regole che utilizzano caratteri jolly ampi a livello di pacchetto o applicano istruzioni di configurazione globali.
  2. Converti l'output r8keepradius.pb in un report HTML interattivo utilizzando KeepRadiusHtmlReportGenerator (incluso in 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
    

    Quando viene fornita una directory, KeepRadiusHtmlReportGenerator esamina tutti i file *keepradius*.pb, genera un report HTML per ogni modulo e crea out/keep_radius_reports/keepradius.html che riassume i conteggi degli elementi attivi, i conteggi degli elementi conservati e le regole di conservazione con il raggio più alto nella build. Per saperne di più sull'interpretazione dei punteggi di riduzione, ottimizzazione e offuscamento nel report generato, consulta Utilizza R8 Configuration Analyzer.

Soong supporta anche variabili di ambiente aggiuntive per la diagnostica e il loop interno di R8 in build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: scrive r8inputs.zip nella directory intermedia del modulo contenente tutti i JAR di input, i JAR della libreria e le configurazioni ProGuard unite per la riproduzione R8 autonoma.
  • R8_DUMP_PERFETTO_TRACE=true: scrive r8trace.ptrace nella directory intermedia del modulo per ispezionare i passaggi di compilazione R8 in Perfetto.
  • SOONG_SKIP_TRACE_REFERENCES=true: ignora l'esecuzione delle regole tracereferences configurate da trace_references_from o trace_references per velocizzare le build del loop interno locale durante l'iterazione sul codice di test.

Convalida le regole del consumer della libreria

Quando un java_library o android_library esporta regole keep agli app per utenti finali downstream (export_proguard_flags_files: true), queste regole non devono includere flag globali che alterano o disattivano l'ottimizzazione per l'app per utenti finali.

R8 fornisce un analizzatore di regole di conservazione open source (ProcessKeepRules), esposto nella build della piattaforma Android come strumento host process-keep-rules (definito in 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>

Lo strumento process-keep-rules analizza ogni file di configurazione e non va a buon fine con la diagnostica di file e righe se rileva istruzioni non consentite nelle regole dei consumer della libreria. Le istruzioni non consentite includono l'ottimizzazione globale, la riduzione o la disattivazione dell'offuscamento (come -dontoptimize o -dontshrink), il repackaging del pacchetto e i flag di modifica dell'accesso, i flag di diagnostica o mappatura e -keepattributes a livello di app.

Controlla gli alberi di origine con pgaudit.py

Per controllare le directory di origine non create insieme agli analizzatori in fase di creazione di R8, l'albero della piattaforma Android include lo script pgaudit.py in build/make/core/proguard/tools/. Questo strumento di analisi statica analizza i file Android.bp, proguard.flags e keep.xml in un repository per segnalare le regole già coperte da AAPT2 o dalle baseline della piattaforma globale, i caratteri jolly generici, gli override di -dontoptimize e le regole di conservazione solo per i test:

# 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

I team di piattaforma e OEM possono combinare pgaudit.py per una rapida classificazione dell'albero di origine, process-keep-rules per convalidare le regole delle librerie esportate e R8_DUMP_KEEP_RADIUS=true con KeepRadiusHtmlReportGenerator per misurare il raggio di conservazione esatto di classi, campi e metodi di ogni regola in una build.