Ottimizzare il codice e le risorse della piattaforma con R8

Il sistema di compilazione della piattaforma Android (Soong) esegue il compilatore R8 su target che compilano bytecode DEX, incluse app per Android (android_app), test (android_test) e librerie Java installabili con compile_dex: true (ad esempio services.jar), per ridurre, ottimizzare ed eliminare codice e risorse inutilizzati. Nelle librerie statiche (android_library e static java_library), 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 li collegano staticamente.

Per gli ingegneri di piattaforma che creano immagini di sistema o pacchetti del fornitore, l'eliminazione di regole di conservazione eccessivamente ampie offre vantaggi diretti per l'integrità del sistema:

  • Ingombro ridotto della partizione di sistema: R8 rimuove classi, metodi e risorse non utilizzati prima di comprimere APK e JAR in /system, /system_ext, /product e /vendor.
  • Artefatti compilati più piccoli: meno metodi DEX significano file .odex e .vdex 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 paging su richiesta, i binari più piccoli riducono gli errori di pagina principali durante l'avvio dell'app e la memoria di codice e risorse residente nei processi. Per ulteriori informazioni su come il codice dell'app compilato influisce sulla memoria del dispositivo, vedi Il codice dell'app è memoria.
  • Ottimizzazione più efficace dell'intero programma: i vincoli di conservazione ristretti consentono a R8 di incorporare i metodi, devirtualizzare l'interfaccia e le chiamate virtuali, eliminare i campi inutilizzati e propagare le costanti tra le classi.

Configurare l'ottimizzazione R8 in Soong

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

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

La tabella seguente riepiloga 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 tutte le destinazioni 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 di test e java_library autonomi, che superano -dontshrink se non impostato esplicitamente).
optimize Controlla le ottimizzazioni del bytecode, come l'incorporamento dei metodi, l'unione delle classi, la propagazione delle costanti e la rimozione dei rami non raggiungibili. 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 file DEX e sbloccare ottimizzazioni più approfondite (vedi Attiva l'offuscamento ove possibile).
shrink_resources Rimuove le risorse inutilizzate (voci res/) 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 il codice e i riferimenti alle risorse congiuntamente in un unico passaggio. Quando 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 annotazioni o valori predefiniti standard.
export_proguard_flags_files Propaga proguard_flags_files di questa libreria ai moduli downstream che dipendono staticamente da essa.
trace_references_from Elenca i target complementari della libreria Java i cui riferimenti bytecode in questo target R8 vengono tracciati e conservati automaticamente.

Abilita l'offuscamento ove possibile

In Soong, obfuscate è impostato su false per compatibilità storica, ma dovresti impostare esplicitamente obfuscate: true su android_app e sui target DEX autonomi ove possibile:

  • Impronta DEX e .vdex più piccola: 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 di tipo. Poiché Android mappa i file .vdex e .odex nella memoria del processo, 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 nei pacchetti che R8 deve altrimenti ignorare 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 mapping proguard_dictionary nella directory intermedia del modulo, raggruppa tutti i dizionari dei moduli nell'artefatto proguard-dict.zip della build, incorpora un hash di mapping (--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.

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

  • Librerie condivise nel bootclasspath o nel system_serverclasspath: i moduli (java_library o java_sdk_library) che espongono un'API esterna vengono collegati dinamicamente da altri moduli in fase di runtime (ad esempio target framework.jar, services.jar o <uses-library>) devono mantenere obfuscate: false o conservare esplicitamente la propria superficie API utilizzando le regole @KeepForApi o -keep (insieme a protect_api_surface: true per i target bootclasspath) in modo che i chiamanti compilati in base ai relativi stub possano risolvere i nomi di classi e membri in fase di runtime.

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

  • Dipendenze APK di test esterni: 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 durante l'esecuzione del test. Preferisci strutturare il test come un APK con strumentazione automatica che collega MyApp.impl in modo statico, annota gli hook di test con @VisibleForTesting o configura trace_references_from (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.
  • Riflessione basata su stringhe e JNI: se un modulo cerca classi, metodi o campi in base a nomi di stringhe letterali (Class.forName, getDeclaredMethod o JNI FindClass e GetMethodID) senza regole di conservazione o annotazioni, l'offuscamento rinomina queste destinazioni e interrompe le ricerche in fase di runtime. Tieni presente che anche la reflection non annotata non supera i test in shrink: true e optimize: true. Annota questi punti di ingresso con Guida per conservare le annotazioni (@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 maggior parte delle regole di conservazione personalizzate sono ridondanti:

  • Baseline della piattaforma e AAPT2: la maggior parte dei punti di ingresso viene già conservata automaticamente dalle baseline della piattaforma globali (come @Keep, metodi JNI native e @VisibleForTesting) o generata da AAPT2 da AndroidManifest.xml e risorse di layout (vedi Informazioni sulle regole di conservazione predefinite della piattaforma).
  • Alternative mirate: nei casi in cui i punti di ingresso 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 ai file .flags separati (vedi Esegui l'audit e la migrazione delle regole di conservazione esistenti).

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

Informazioni sulle regole di conservazione predefinite della piattaforma

Soong trasmette 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) all'interno dei 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 stack trace, annotazioni di visibilità di runtime (RuntimeVisible*Annotations), attributi Exceptions e AnnotationDefault, metodi native, membri Serializable, metodi @JavascriptInterface, costruttori Throwable(String), campi Parcelable$CREATOR e campi MessageLite protobuf.
  • build/make/core/proguard/kotlin.flags: disattiva 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 rilascio.
  • build/make/core/proguard/checknotnull.flags: sostituisce le chiamate di assistenza per il controllo dei valori null comuni (com.google.common.base.Preconditions.checkNotNull e dagger.internal.Preconditions.checkNotNull*) con controlli dei valori null del bytecode concisi. Questo file omette intenzionalmente Objects.requireNonNull per preservare messaggi di eccezione espliciti nei limiti dell'API del framework.
  • build/make/core/proguard/enumvalues.flags: mantiene i metodi values e valueOf sui tipi enum, a meno che non siano disattivati dalla configurazione della build.
  • Regole generate automaticamente da AAPT2: AAPT2 esamina i file XML AndroidManifest.xml, di layout e delle preferenze uniti 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.

Controllare ed eseguire la migrazione delle regole di conservazione esistenti

Quando esegui l'audit dei file proguard.flags o keep.xml esistenti in un repository della piattaforma, valuta ogni regola in ordine in base 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 del manifest AAPT2 o dai valori predefiniti di R8, nonché le regole che fanno riferimento a classi o pacchetti che non esistono più.

Se tutte le regole in un file .flags sono ridondanti, elimina il file e rimuovi proguard_flags_files da Android.bp. Se il blocco optimize rimanente riporta solo le impostazioni predefinite, rimuovi il blocco ridondante e formatta il file di build con bpfmt -w Android.bp. Quando pulisci un pacchetto, controlla se i moduli complementari nelle sottodirectory (ad esempio 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 sui test

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

  • Collega la libreria di implementazione ai test (static_libs): quando un android_test esercita dettagli di implementazione interni o privati del pacchetto di un'app, l'architettura della piattaforma consigliata prevede di inserire i file sorgente dell'app in un android_library (MyApp.impl) e di collegare staticamente MyApp.impl sia a MyApp che 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 tre moduli consente a MyAppTests di essere eseguito come test di autostrumentazione con accesso completo alle classi interne, consente a MyApp di attivare obfuscate: true liberamente senza interrompere i test, evita la spedizione di punti di ingresso solo di test nell'APK di produzione ed elimina la ricompilazione di MyApp quando il codice di test cambia.

  • Annota gli hook di test con @VisibleForTesting: se un APK di test esterno chiama un numero ridotto di metodi o costruttori interni su una destinazione android_app e la ristrutturazione in una libreria .impl è impraticabile, annota queste dichiarazioni con @VisibleForTesting. La baseline build/make/core/proguard.flags globale conserva @VisibleForTesting gli elementi nei pacchetti android.**, com.android.** e com.google.android.** automaticamente senza file .flags personalizzati. (Per i moduli del fornitore al di fuori di questi spazi dei nomi, utilizza @UsedByReflection o le regole della libreria esportata.)

  • Utilizza trace_references_from oltre i limiti della libreria della piattaforma: quando i test non possono collegare staticamente l'implementazione di destinazione (ad esempio, i test che utilizzano i servizi del server di sistema o i file JAR della piattaforma come framework-connectivity), configura trace_references_from nel modulo di destinazione in modo che punti a un modulo complementare della libreria Java contenente le origini dei test. R8 traccia e conserva tutte le classi e tutti i membri a cui fa riferimento il bytecode complementare.

Passaggio 3: esporta le regole dalla libreria proprietaria

Se una java_library o android_library condivisa richiede regole di conservazione per la propria riflessione interna o callback JNI, definisci le regole nel 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 delle librerie downstream che collegano staticamente my-shared-library ereditano automaticamente queste regole, quindi le app downstream non devono duplicarle. Quando più moduli in directory diverse devono condividere un insieme 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 tra i limiti del pacchetto. Per indicazioni generali sulla creazione di regole di conservazione per i consumatori 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, a un metodo, a un costruttore o a un campo tramite reflection o JNI e non sono coperti da AAPT2 o dalle baseline globali, sostituisci le regole -keep separate nei file .flags con le annotazioni di origine della Guida alle annotazioni Keep (vedi il riferimento Javadoc keepanno):

  • @UsesReflection: preferisci questa annotazione quando controlli il codice che esegue la reflection. Posizionalo nel sito di chiamata di reflection per dichiarare dinamicamente le classi, i metodi o i campi di destinazione a cui si accede. 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: posiziona su classi, metodi, campi o costruttori di cui viene creata un'istanza o richiamati in modo riflessivo da codice o librerie esterni, ad esempio classi caricate con Class.forName da chiavi Bundle o Settings o classi di plug-in caricate in class loader 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 reflection esatto e possa comunque ottimizzare o eliminare i membri inutilizzati della classe. Non aggiungere @UsedByReflection ai componenti registrati nel manifest come JobService o BroadcastReceiver, perché AAPT2 li conserva già.
  • @UsedByNative: posiziona i metodi o i campi a cui si accede dal codice JNI C o C++ utilizzando GetMethodID, GetStaticMethodID o GetFieldID.
  • @KeepForApi: posiziona le classi o i membri dell'API della libreria che devono rimanere intatti quando una libreria viene ridotta prima della distribuzione.
  • @Keep (androidx.annotation.Keep): utilizza questo valore come fallback quando keepanno non è applicabile. L'applicazione di @Keep a una classe mantiene la classe e tutti i relativi membri senza condizioni. L'applicazione di @Keep a un metodo o a un campo funge da punto di ingresso incondizionato che mantiene il membro e la relativa classe contenitore anche se la classe non viene mai istanziata, mentre keepanno esprime la raggiungibilità condizionale.

Per utilizzare le annotazioni keepanno di R8 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 interno di regole di conservazione e rimuove le annotazioni dall'output DEX finale, aggiungendo un sovraccarico di bytecode di runtime pari a zero.

Evitare 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 sul rendimento: questa regola è completamente ridondante con AAPT2. Poiché utilizza un carattere jolly senza verificare se il componente è registrato nel AndroidManifest.xml unito finale, R8 è costretto 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 esamina il manifest unito e genera regole -keep esatte per i componenti registrati.

Regole di conservazione del gestore di clic XML generico

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Perché influisce sul rendimento: la conservazione di ogni metodo public void *(View) in ogni classe del modulo impedisce a R8 di rimuovere o incorporare 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.

Getter e setter di Broad View mantengono le regole

# 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 sulle prestazioni: questa regola impone a R8 di 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 inutilizzati e l'incorporamento nel codice dell'interfaccia utente.
  • Correzione consigliata: elimina la regola. AAPT2 conserva già i costruttori per le classi View personalizzate inflate dai file XML di layout. Se il codice anima una proprietà di 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 FloatProperty o IntProperty) per evitare completamente la riflessione. Se non è possibile evitare l'accesso alle proprietà di riflessione, annota il getter o il setter specifico con @UsedByReflection.

L'ottimizzazione o l'offuscamento generico disattiva

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Perché influisce sul rendimento: 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. Peggio ancora, se una libreria esporta un file .flags contenente -dontoptimize, disattiva l'ottimizzazione R8 per ogni android_app downstream che collega la 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) nel target foglia.

Flag di diagnostica nelle regole di commit

# 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 le mappature e gli output di utilizzo di R8, ad esempio proguard_dictionary e proguard_usage.zip, nella directory intermedia del modulo in out/soong/.intermediates/.

Regole di conservazione della produzione per il codice di test

  • Perché influisce negativamente sul rendimento: l'aggiunta di regole -keep personalizzate esclusivamente per l'accesso ai test impone che il codice rimanga non offuscato e conservato nelle build di produzione. Sebbene l'annotazione dei metodi con @VisibleForTesting o la configurazione di trace_references_from eviti di gestire regole -keep manuali, questi simboli vengono comunque inclusi nel binario di produzione.
  • Correzione consigliata: per escludere completamente i punti di ingresso solo di test dall'APK di produzione, struttura l'applicazione in una libreria .impl e collegala a un APK di test auto-strumentato utilizzando static_libs, come descritto nel passaggio 2: esegui la migrazione delle regole di conservazione basate sui test.

Caratteri jolly per le 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 sul rendimento: i caratteri jolly generici conservano ogni risorsa nel tipo corrispondente in tutto il modulo e le relative dipendenze, vanificando la riduzione delle risorse (shrink_resources: true) e gonfiando la tabella APK e resources.arsc mappata.
  • Correzione consigliata:
    • Preferisci i riferimenti alle risorse statiche 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 risorse disegnabili a tema. Utilizza invece un'istruzione switch in fase di compilazione o una mappatura su costanti statiche R.id, R.string o R.drawable. I riferimenti statici consentono a R8 e AAPT2 di tracciare le risorse live esatte, eliminare l'overhead di ricerca delle stringhe in fase di runtime e rimuovere completamente la necessità di keep.xml.
    • Elenca nomi di risorse specifici: se è necessaria la ricerca dinamica, ad esempio da parte di un lettore di licenze di terze parti, elenca gli identificatori di risorse esatti 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 in XML (@raw/foo), lo strumento di riduzione le conserva automaticamente, quindi puoi eliminare res/raw/keep.xml.

Verificare e controllare le modifiche alle regole

Ogni volta che rimuovi o restringi le regole di conservazione in proguard.flags o keep.xml, verifica che il modulo venga compilato correttamente, superi i test unitari e di strumentazione e mantenga tutti i punti di ingresso richiesti.

Creare e testare i moduli

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

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

    atest <TEST_MODULE_NAME>
    
  3. Per app di sistema, servizi privilegiati o moduli dipendenti dall'hardware, esegui test di strumentazione e UI su dispositivi fisici di destinazione o nel laboratorio di test dei dispositivi per esercitare percorsi di reflection di runtime, binding IPC e inflazione delle risorse.

Ispezionare le differenze tra 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 inutilizzati senza eliminare i punti di ingresso previsti:

  • Utilizza apkanalyzer o dexdump per esaminare classi, metodi e 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 mantenga quelle necessarie:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    

Analizzare il raggio di conservazione e l'inclusione 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 l'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 di conservazione e annotazione keepanno, R8 registra:

    • Raggio di conservazione immediata: le classi, i campi e i metodi esatti conservati dalla regola, insieme ai vincoli specifici che impone contro la riduzione, l'ottimizzazione o l'offuscamento.
    • Subsumption della regola: 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 base globale, puoi eliminarla in sicurezza.
    • Regole globali e a livello di pacchetto: regole che utilizzano caratteri jolly generici a livello di pacchetto o che applicano direttive 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
    

    Se 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 riepilogano i conteggi degli elementi live, gli elementi conservati e le regole di conservazione con il raggio più ampio nella build. Per saperne di più sull'interpretazione dei punteggi di riduzione, ottimizzazione e offuscamento nel report generato, consulta Utilizzare R8 Configuration Analyzer.

Soong supporta anche due variabili di ambiente di diagnostica R8 aggiuntive in build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: scrive r8inputs.zip nella directory intermedia del modulo contenente tutti i file JAR di input, i file JAR della libreria e le configurazioni ProGuard unite per la riproduzione di R8 standalone.
  • R8_DUMP_PERFETTO_TRACE=true: scrive r8trace.ptrace nella directory intermedia del modulo per ispezionare le pass di compilazione R8 in Perfetto.

Convalidare le regole di consumo della libreria

Quando le regole di conservazione vengono mantenute per i consumatori downstream (export_proguard_flags_files: true) in un'esportazione java_library o android_library, queste regole non devono includere flag globali che alterano o disattivano l'ottimizzazione per l'app di consumo.

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 genera un errore con diagnostica di file e riga se rileva direttive non consentite nelle regole di consumo della libreria. Le direttive non consentite includono l'ottimizzazione globale, la riduzione o la disattivazione dell'offuscamento (ad esempio -dontoptimize o -dontshrink), i flag di riconfezionamento e modifica dell'accesso dei pacchetti, i flag di diagnostica o mappatura e -keepattributes a livello di app.

Controllare gli alberi delle origini con pgaudit.py

Per controllare le directory di origine non compilate insieme agli analizzatori in fase di compilazione di R8, l'albero della piattaforma Android include lo script pgaudit.py in build/make/core/proguard/tools/. Questo strumento di analisi statica esegue la scansione dei 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, dai caratteri jolly generici, -dontoptimize dagli override e dalle 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 della piattaforma e degli OEM possono combinare pgaudit.py per la classificazione rapida dell'albero delle origini, process-keep-rules per convalidare le regole della libreria 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.