Plattformcode und ‑ressourcen mit R8 optimieren

Das Build-System der Android-Plattform (Soong) führt den R8-Compiler auf Zielen aus, die DEX-Bytecode kompilieren, einschließlich Android-Apps (android_app), Tests (android_test) und installierbaren Java-Bibliotheken mit compile_dex: true (z. B. services.jar), um ungenutzten Code und ungenutzte Ressourcen zu verkleinern, zu optimieren und zu entfernen. Bei statischen Bibliotheken (android_library und statische java_library) führt Soong R8 nicht direkt aus, sondern verwendet den Attributblock optimize, um Consumer-Keep-Regeln (export_proguard_flags_files: true) an nachgelagerte DEX-Ziele anzuhängen und weiterzugeben, die sie statisch verknüpfen.

Für Plattform-Entwickler, die System-Images oder Anbieterpakete erstellen, bietet das Entfernen zu weit gefasster Beibehaltungsregeln direkte Vorteile für die Systemintegrität:

  • Geringerer Speicherbedarf der Systempartition:R8 entfernt ungenutzte Klassen, Methoden und Ressourcen, bevor APKs und JARs auf /system, /system_ext, /product und /vendor gepackt werden.
  • Kleinere kompilierte Artefakte:Weniger DEX-Methoden bedeuten kleinere .odex- und .vdex-Dateien, die von dex2oat während der Build-Zeit oder der Kompilierung auf dem Gerät generiert werden.
  • Geringere Arbeitsspeicherauslastung zur Laufzeit:Da Android .odex, .vdex und APK-Ressourcentabellen mithilfe von mmap-Aufrufen mit Demand Paging in den Prozessspeicher abbildet, reduzieren kleinere Binärdateien die Anzahl der Seitenfehler beim App-Start und verringern den residenten Code- und Ressourcenspeicher über Prozesse hinweg. Weitere Informationen dazu, wie sich kompilierter App-Code auf den Gerätespeicher auswirkt, finden Sie unter App-Code ist Speicher.
  • Effektivere Optimierung des gesamten Programms: Durch engere Keep-Einschränkungen kann R8 Methoden inline einfügen, Schnittstellen- und virtuelle Aufrufe devirtualisieren, nicht verwendete Felder entfernen und Konstanten über Klassen hinweg weitergeben.

R8-Optimierung in Soong konfigurieren

Konfigurieren Sie in Android.bp-Dateien die R8-Optimierung mit dem Attributblock optimize für android_app- und installierbare java_library-Ziele (oder für android_library- und statische java_library-Module, um Consumer-Keep-Regeln zu exportieren):

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

In der folgenden Tabelle sind die gängigsten optimize-Eigenschaften in Soong (definiert in build/soong/java/dex.go) zusammengefasst:

Attribut Beschreibung
enabled Steuert, ob R8 auf dem Ziel ausgeführt wird. Die Standardeinstellung ist true für alle android_app-Ziele.
shrink Steuert das Tree-Shaking, um nicht erreichbare Klassen, Felder und Methoden zu entfernen. Der Standardwert ist true für android_app-Ziele (false für eigenständige java_library- und Testmodule, die -dontshrink übergeben, sofern nicht explizit festgelegt).
optimize Steuert Bytecode-Optimierungen wie Method-Inlining, Zusammenführen von Klassen, konstante Weitergabe und Entfernen von ungenutzten Verzweigungen. Der Standardwert ist true für android_app-Ziele (gesteuert durch das Release-Build-Flag RELEASE_R8_OPTIMIZE_BY_DEFAULT).
obfuscate Steuert die Minimierung und Umbenennung von Kennungen. Der Standardwert ist false, um die Kompatibilität mit älteren Versionen zu gewährleisten. Setzen Sie ihn jedoch nach Möglichkeit auf true, um die DEX-Größe zu reduzieren und tiefgreifendere Optimierungen zu ermöglichen (siehe Verschleierung nach Möglichkeit aktivieren).
shrink_resources Entfernt nicht verwendete Ressourcen (res/-Einträge) nach der Codekomprimierung aus dem gepackten APK. Die Standardeinstellung ist false.
optimized_shrink_resources Führt die integrierte R8-Pipeline zur Code- und Ressourcenminimierung aus, sodass R8 Code- und Ressourcenreferenzen gemeinsam in einem einzigen Durchlauf verfolgt. Wenn shrink_resources: true festgelegt ist, wird standardmäßig RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT verwendet (true in Standard-Plattform-Builds).
proguard_flags_files Listet modulspezifische .flags- oder .pro-Dateien auf, die benutzerdefinierte Beibehaltungsregeln enthalten. Fügen Sie keine benutzerdefinierten Dateien hinzu, wenn Standardanmerkungen oder Standardeinstellungen ausreichen.
export_proguard_flags_files Gibt die proguard_flags_files dieser Bibliothek an nachgelagerte Module weiter, die statisch davon abhängen.
trace_references_from Listet direkte DEX-kompilierungsabhängige Ziele auf (verbunden über instrumentation_for oder libs), deren Bytecode-Verweise in dieses Ziel von R8 automatisch verfolgt und beibehalten werden (auch über override_android_app- und override_android_test-Varianten weitergegeben und für nicht abhängige Java-Begleitbibliotheken unterstützt, wenn sie mit einem .impl-Split gekoppelt sind).
allow_traced_references Ermöglicht es, dass direkt DEX-kompilierende abhängige Ziele (verbunden über instrumentation_for oder libs, z. B. Repository-übergreifende oder Anbieter-Tests, die nicht in trace_references_from aufgeführt werden können) die Verfolgung ihrer Bytecode-Referenzen in dieses Ziel aktivieren, indem trace_references: true festgelegt wird.
trace_references Wenn für eine direkte DEX-kompilierende Abhängigkeit eines Ziels mit allow_traced_references: true auf true gesetzt, wird R8 tracereferences für dieses Modul ausgeführt und die generierten Beibehaltungsregeln werden an das Zielmodul übergeben.
protect_api_surface Übergibt --protect-api-surface an R8. Wenn diese Option für ein java_sdk_library festgelegt ist, werden automatisch Metalava API-Keep-Regeln für alle aktivierten API-Bereiche (public, system, module-lib, system-server und test) generiert und an die Implementierungsbibliothek angehängt.

Statische Bibliotheken (android_library, java_library und AAR-Importe) können auch required_downstream_optimization: { enabled: true, optimize: true } in Android.bp festlegen, um zu erzwingen, dass alle nachgelagerten DEX-Kompilierungsziele, die sie statisch (direkt oder transitiv) verknüpfen, die R8-Verkleinerung und Bytecode-Optimierung aktivieren. Soong aktiviert dies automatisch auf androidx.compose.runtime_runtime, sodass Ziele, die mit Jetpack Compose verknüpft sind, mit R8 optimiert werden.

Verschleierung nach Möglichkeit aktivieren

In Soong ist obfuscate aus Gründen der Abwärtskompatibilität standardmäßig auf false festgelegt. Du solltest jedoch nach Möglichkeit obfuscate: true für android_app und eigenständige DEX-Ziele explizit festlegen:

  • Kleinerer DEX- und .vdex-Speicherbedarf: Durch das Umbenennen von Paketen, Klassen, Feldern und Methoden in kurze Kennungen (a, b) werden der DEX-Stringpool (string_ids und string_data_item) und die Typdeskriptoren verkleinert. Da Android .vdex- und .odex-Dateien dem Prozessspeicher zuordnet, reduzieren kleinere Symboltabellen sowohl die Größe der Systempartition als auch den Speicherbedarf zur Laufzeit.
  • Tiefere Optimierungen des gesamten Programms:Wenn Sie R8 erlauben, Kennungen umzubenennen, werden Klassen zusammengeführt, Pakete vereinfacht und synthetische Klassen über Pakete hinweg dedupliziert, die R8 sonst überspringen müsste, um Namenskonflikte zu vermeiden.
  • Vollständige Stacktrace-Symbolisierung:Wenn die Obfuskation in Soong aktiviert wird, wird die Debugging-Fähigkeit nicht beeinträchtigt. Für jedes mit R8 kompilierte Ziel gibt Soong eine proguard_dictionary-Zuordnungsdatei im Zwischenverzeichnis des Moduls aus, bündelt alle Modulwörterbücher im proguard-dict.zip-Artefakt des Builds, bettet einen Zuordnungshash (--map-id-template) in den DEX-Header ein und schreibt die SourceFile-Attribute (--source-file-template) der Klasse neu, damit retrace Stacktraces automatisch symbolisieren kann.

Behalten Sie obfuscate: false (oder explizit öffentliche API-Namen) nur für Folgendes bei:

  • Gemeinsam genutzte Bibliotheken im Bootclasspath oder system_server-Classpath: Module (java_library oder java_sdk_library), die eine externe API-Oberfläche bereitstellen, die zur Laufzeit dynamisch von anderen Modulen verknüpft wird (z. B. framework.jar-, services.jar- oder <uses-library>-Ziele), müssen entweder obfuscate: false beibehalten oder ihre API-Oberfläche explizit mit protect_api_surface: true-, @KeepForApi- oder -keep-Regeln beibehalten, damit Aufrufer, die mit ihren Stubs kompiliert wurden, Klassen- und Mitgliedsnamen zur Laufzeit auflösen können. Bei java_sdk_library-Modulen werden durch das Festlegen von protect_api_surface: true automatisch Metalava-ProGuard-Keep-Regeln für alle aktivierten API-Bereiche (public, system, module-lib, system-server und test) generiert und an die Implementierungsbibliothek angehängt. Wenn die allgemeine R8-Optimierung für nicht aktualisierbare Bootclasspath-Ziele (framework-minus-apex, ext und framework-location) aktiviert ist, enthalten sie auch framework-hiddenapi-proguard-rules (generiert von hiddenapi_to_proguard), um die in aktiven boot/hiddenapi/*.txt-Dateien aufgeführten Elemente beizubehalten (ohne hiddenapi-max-target-o.txt).

Prüfen Sie vor dem Aktivieren von obfuscate: true für eine Anwendung die folgenden Migrationsvoraussetzungen:

  • Externe Test-APK-Abhängigkeiten: Wenn ein externes android_test-APK instrumentation_for: "MyApp" festlegt und interne Klassen oder Methoden von MyApp direkt aufruft, führt das Umbenennen dieser Symbole zur Testlaufzeit zu NoSuchMethodError oder NoClassDefFoundError. Konfiguriere trace_references_from (oder allow_traced_references mit trace_references: true), strukturiere den Test als selbstinstrumentierendes APK, das statisch mit MyApp.impl verknüpft ist, oder annotiere Test-Hooks mit @VisibleForTesting (siehe Schritt 2: Testgesteuerte Keep-Regeln migrieren), damit die für Tests erforderlichen internen Symbole beibehalten werden, während der Rest der App verschleiert wird.
  • Stringbasierte Reflexion und JNI: Wenn ein Modul Klassen, Methoden oder Felder anhand von Stringliteralen (Class.forName, getDeclaredMethod oder JNI FindClass und GetMethodID) ohne Beibehaltungsregeln oder Annotationen sucht, werden diese Ziele durch die Verschleierung umbenannt und die Laufzeitsuchen funktionieren nicht mehr. Beachten Sie, dass die nicht annotierte Reflexion auch unter shrink: true und optimize: true fehlschlägt. Annotieren Sie diese Einstiegspunkte mit dem Leitfaden zum Beibehalten von Annotationen (@UsesReflection, @UsedByReflection oder @UsedByNative), damit R8 ihre Namen beibehält und den Rest des Moduls verschleiert.

Prinzip „Keine benutzerdefinierten Flags“ befolgen

Das ideale Android-Plattformmodul enthält keine benutzerdefinierten proguard.flags- oder keep.xml-Dateien. Im Build der Android-Plattform sind die meisten benutzerdefinierten Beibehaltungsregeln redundant:

  • Plattform-Baselines und AAPT2: Die meisten Einstiegspunkte werden bereits automatisch durch globale Plattform-Baselines (wie @Keep, JNI-native-Methoden und @VisibleForTesting) beibehalten oder von AAPT2 aus AndroidManifest.xml und Layoutressourcen generiert (siehe Standardmäßige Beibehaltungsregeln der Plattform).
  • Gezielte Alternativen: Wenn Einstiegspunkte beibehalten werden müssen, sollten Sie Deklarations-Site-Annotationen (keepanno, @VisibleForTesting), exportierte Bibliotheksregeln (export_proguard_flags_files: true) oder statische Testverknüpfungen gegenüber separaten .flags-Dateien bevorzugen (siehe Vorhandene Beibehaltungsregeln prüfen und migrieren).

Bevor Sie eine benutzerdefinierte proguard.flags- oder keep.xml-Datei hinzufügen oder beibehalten, prüfen Sie, ob die Regel bereits von Standard-Baselines verarbeitet wird oder zu Codeannotationen migriert werden kann.

Standardmäßige Behaltensregeln der Plattform

Soong übergibt die folgenden globalen Baseline-Regeln automatisch an jeden R8-Aufruf für DEX-Kompilierungsziele (android_app, android_test und installierbare java_library-Module), die in build/soong/java/dex.go konfiguriert sind:

  • build/make/core/proguard.flags: Behält Klassen und Mitglieder bei, die mit @com.android.internal.annotations.VisibleForTesting in allen Paketen und mit @VisibleForTesting (androidx.annotation.VisibleForTesting oder com.google.common.annotations.VisibleForTesting) in den Paketen android.**, com.android.** und com.google.android.** annotiert sind. Außerdem werden @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback und @dalvik.annotation.optimization.** beibehalten.
  • build/make/core/proguard_basic_keeps.flags: Behält SourceFile-Attribute für Stacktraces, Laufzeitsichtbarkeits-Annotationen (RuntimeVisible*Annotations), Exceptions- und AnnotationDefault-Attribute, native-Methoden, Serializable-Mitglieder, @JavascriptInterface-Methoden, Throwable(String)-Konstruktoren, Parcelable$CREATOR-Felder und Protobuf-MessageLite-Felder bei.
  • build/make/core/proguard/kotlin.flags: Unterdrückt harmlose Warnungen für bestimmte Kotlin-Meta-Annotationen (kotlin.Metadata und kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) und entfernt Kotlin-DebugMetadata-Annotationen in Release-Builds.
  • build/make/core/proguard/checknotnull.flags: Ersetzt gängige Null-Prüfungs-Hilfsaufrufe (com.google.common.base.Preconditions.checkNotNull und dagger.internal.Preconditions.checkNotNull*) durch prägnante Bytecode-Null-Prüfungen. In dieser Datei wird Objects.requireNonNull absichtlich weggelassen, um explizite Ausnahmemeldungen über Framework-API-Grenzen hinweg beizubehalten.
  • build/make/core/proguard/enumvalues.flags: Behält die Methoden values und valueOf für enum-Typen bei, sofern sie nicht durch die Build-Konfiguration deaktiviert wurden.
  • Automatisch generierte AAPT2-Regeln: AAPT2 prüft zusammengeführte AndroidManifest.xml-, Layout-XML- und Präferenz-XML-Dateien, um genaue Beibehaltungsregeln für jede registrierte Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, benutzerdefinierte View-Unterklasse, Preference-Unterklasse und XML-android:onClick-Methode zu generieren.

Bestehende Beibehaltungsregeln prüfen und migrieren

Wenn Sie vorhandene proguard.flags- oder keep.xml-Dateien in einem Plattform-Repository prüfen, bewerten Sie jede Regel der Reihe nach anhand der folgenden vierstufigen Hierarchie:

Schritt 1: Redundante oder veraltete Regeln löschen

Lösche Regeln, die bereits durch globale Baselines, AAPT2-Manifest- und Layoutregeln oder R8-Standardeinstellungen abgedeckt sind, sowie Regeln, die auf nicht mehr vorhandene Klassen oder Pakete verweisen.

Wenn alle Regeln in einer .flags-Datei redundant sind, löschen Sie die Datei und entfernen Sie proguard_flags_files aus Android.bp. Wenn der verbleibende optimize-Block nur Standardeinstellungen wiederholt, entfernen Sie den redundanten Block und formatieren Sie die Build-Datei mit bpfmt -w Android.bp. Prüfen Sie beim Bereinigen eines Pakets, ob Companion-Module in Unterverzeichnissen (z. B. Kotlin-Zielvarianten) auf dieselbe Flag-Datei verweisen, und aktualisieren Sie sie gemeinsam.

Schritt 2: Testgesteuerte Aufbewahrungsregeln migrieren

Verschieben Sie testgesteuerte Beibehaltungsregeln aus benutzerdefinierten .flags-Produktionsdateien mit einem der folgenden Muster:

  • Verweise von Test- oder Companion-Zielen (trace_references_from oder allow_traced_references) verfolgen: Wenn sowohl das Ziel als auch ein direkt abhängiges Modul ihre eigene DEX (android_app, android_test oder eine dexierte java_library) kompilieren und über instrumentation_for oder libs verbunden sind, kann Soong R8 tracereferences auf dem abhängigen Modul ausführen und die generierten Beibehaltungsregeln in die R8-Kompilierung des Ziels einspeisen, ohne einen Abhängigkeitszyklus zu erstellen oder eine manuelle .impl-Bibliotheksaufteilung zu erfordern:

    • Tests im selben Repository (trace_references_from): Listet die direkten instrumentation_for- oder libs-Abhängigkeiten des Zielmoduls auf. Dies wird auch automatisch über override_android_app- und override_android_test-Varianten weitergegeben und unterstützt weiterhin nicht abhängige Java-Begleitbibliotheken, wenn sie mit einem .impl-Split gekoppelt sind:

      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"],
      }
      
    • Repository- oder herstellerübergreifende Tests (allow_traced_references und trace_references): Wenn das Zielmodul die Abhängigkeit nicht direkt in trace_references_from auflisten kann (z. B. Tests in einem anderen Repository oder Herstellerbaum), legen Sie allow_traced_references: true für das Zielmodul fest und aktivieren Sie die Option für jede Abhängigkeit mit 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,
          },
      }
      
    • Stringbasierte Reflexion in Tests: tracereferences prüft Bytecode-Konstantenpool-Referenzen und kann keine stringbasierte Reflexion in Tests erkennen (Class.forName, getDeclaredField, getDeclaredMethod oder reflektierende Feldinjektions-Hilfsprogramme). Annotieren Sie Produktionsfelder und -methoden, auf die reflektiv zugegriffen wird, mit @VisibleForTesting (oder @UsedByReflection).

  • Implementierungsbibliothek in Tests verknüpfen (static_libs): Wenn Sie keine reinen Testeinstiegspunkte im Produktions-APK beibehalten möchten und nicht möchten, dass Teständerungen R8 auf dem Produktionsziel noch einmal auslösen, platzieren Sie die App-Quelldateien in einem android_library (MyApp.impl) und verknüpfen Sie MyApp.impl statisch mit MyApp und 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"],
    }
    

    Dieses 3-Modul-Muster ermöglicht es, MyAppTests als selbstinstrumentierenden Test mit vollem Zugriff auf interne Klassen auszuführen, während MyApp rein für Produktionseinstiegspunkte verkleinert und verschleiert werden kann.

  • Test-Hooks mit @VisibleForTesting annotieren: Wenn ein externes Test-APK nur wenige interne Methoden oder Konstruktoren für ein android_app-Ziel aufruft, können Sie diese Deklarationen mit @VisibleForTesting annotieren. Die globale build/make/core/proguard.flags-Baseline behält @VisibleForTesting-Elemente in den Paketen android.**, com.android.** und com.google.android.** automatisch ohne benutzerdefinierte .flags-Dateien bei. Verwenden Sie für Herstellermodule außerhalb dieser Namespaces trace_references_from, @UsedByReflection oder exportierte Bibliotheksregeln. Wenn Sie manuell mit @VisibleForTesting annotieren, anstatt trace_references_from oder trace_references zu verwenden, annotieren Sie auch Klassen, die an Mockito mock(...) oder spy(...) übergeben werden (nicht nur die Stub-Methoden), da mockito-target (dexmaker-mockito) auf dem Gerät Laufzeit-Unterklassen generiert und R8 Klassen ohne Unterklassen als final (MockitoException: Can't mock/spy class ... - final class) markieren kann.

  • Sich überschneidende statische Bibliotheken (jarjar_rules) vermeiden oder neu verpacken: Wenn sowohl ein android_app als auch das zugehörige instrumentation_for-Test-APK dieselbe Bibliothek statisch verknüpfen (direkt, z. B. guava, oder transitiv), sieht tracereferences diese im Test definierten Klassen und gibt keine Keep-Regeln für die App aus. Zur Laufzeit überschattet der übergeordnete Classloader der App die Kopie des Tests mit seinen durch R8 verkleinerten Klassen, was zu IllegalAccessError oder NoSuchMethodError führt. Wenn der Test keine eigene gebündelte Kopie benötigt, entfernen Sie das Duplikat aus der static_libs des Tests (oder verknüpfen Sie es über libs), um den Konflikt zu vermeiden. Andernfalls packen Sie die sich überschneidenden statischen Bibliothekspakete der App mit jarjar_rules unter einem modulspezifischen Präfix neu.

Schritt 3: Regeln aus der Inhaberbibliothek exportieren

Wenn für eine freigegebene java_library oder android_library Aufbewahrungsregeln für die eigene interne Reflexion oder JNI-Callbacks erforderlich sind, definieren Sie die Regeln für das Bibliotheksziel und legen Sie export_proguard_flags_files: true fest:

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

Alle nachgelagerten android_app- und Bibliotheksziele, die statisch mit my-shared-library verknüpft sind, übernehmen diese Regeln automatisch. Nachgelagerte Apps müssen sie also nicht duplizieren. Wenn mehrere Module in verschiedenen Verzeichnissen einen Regelsatz gemeinsam nutzen müssen, der nicht an eine einzelne Codebibliothek gebunden ist, veröffentlichen Sie die Datei .flags über eine explizite filegroup in Android.bp, damit Module über Paketgrenzen hinweg sauber auf :my-shared-flags verweisen können. Allgemeine Informationen zum Erstellen von Keep-Regeln für Bibliotheksnutzer, ohne die Optimierung nachgelagerter Apps einzuschränken, finden Sie unter Optimierung für Bibliotheksautoren.

Schritt 4: Deklarationen im Quellcode annotieren

Wenn auf eine Klasse, eine Methode, einen Konstruktor oder ein Feld über Reflection oder JNI zugegriffen wird und diese nicht von AAPT2 oder globalen Referenzen abgedeckt werden, ersetzen Sie die losgelösten -keep-Regeln in .flags-Dateien durch Quellanmerkungen aus dem Leitfaden zum Beibehalten von Anmerkungen (siehe keepanno-Javadoc-Referenz):

  • @UsesReflection: Verwenden Sie diese Annotation, wenn Sie den Code steuern, der die Reflexion ausführt. Platzieren Sie sie an der Aufrufstelle der Reflexion, um zu deklarieren, auf welche Zielklassen, ‑methoden oder ‑felder dynamisch zugegriffen wird. Da @UsesReflection automatisch eine Vorbedingung codiert, dass die annotierte Aufrufstelle selbst erreichbar ist, entfernt R8 sowohl den Aufrufer als auch das reflektierende Ziel, wenn die Aufrufstelle nicht verwendet wird.
  • @UsedByReflection: Platzieren Sie diese Annotation auf Klassen, Methoden, Feldern oder Konstruktoren, die von externem Code oder externen Bibliotheken reflektiv instanziiert oder aufgerufen werden, z. B. Klassen, die mit Class.forName aus Bundle- oder Settings-Schlüsseln geladen werden, oder Plug-in-Klassen, die über dynamische Klassenlader geladen werden. Geben Sie kind (z. B. KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) und Parametereinschränkungen an, damit R8 nur den genauen reflektierenden Vertrag beibehält und nicht verwendete Mitglieder der Klasse weiterhin optimieren oder entfernen kann. Füge @UsedByReflection nicht zu im Manifest registrierten Komponenten wie JobService oder BroadcastReceiver hinzu, da AAPT2 sie bereits behält.
  • @UsedByNative: Wird auf Methoden oder Felder angewendet, auf die über C- oder C++-JNI-Code mit GetMethodID, GetStaticMethodID oder GetFieldID zugegriffen wird.
  • @KeepForApi: Wird auf Bibliotheks-API-Klassen oder -Mitglieder angewendet, die intakt bleiben müssen, wenn eine Bibliothek vor der Verteilung verkleinert wird.
  • @Keep (androidx.annotation.Keep): Als Fallback verwenden, wenn keepanno nicht anwendbar ist. Wenn Sie @Keep auf eine Klasse anwenden, werden die Klasse und alle ihre Mitglieder bedingungslos beibehalten. Wenn Sie @Keep auf eine Methode oder ein Feld anwenden, wird ein bedingungsloser Einstiegspunkt erstellt, der das Mitglied und die enthaltende Klasse beibehält, auch wenn die Klasse nie instanziiert wird. keepanno drückt dagegen eine bedingte Erreichbarkeit aus.

So verwenden Sie R8-keepanno-Annotationen in einem Soong-Modul:

  1. Fügen Sie "keepanno-annotations" zu libs in Android.bp hinzu:

    libs: [
        "keepanno-annotations",
    ],
    
  2. Importieren Sie com.android.tools.r8.keepanno.annotations.* und annotieren Sie die Deklaration oder Aufrufstelle im Java- oder Kotlin-Quellcode:

    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 übersetzt keepanno-Annotationen direkt in sein internes Keep-Rule-Modell und entfernt die Annotationen aus der endgültigen DEX-Ausgabe, ohne dass Laufzeit-Bytecode-Overhead entsteht.

Häufige Fehler vermeiden

In den folgenden Abschnitten werden häufige proguard.flags- und keep.xml-Fehler im Plattformcode und deren Behebung beschrieben.

Allgemeine Aufbewahrungsregeln für Komponenten

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Warum die Leistung beeinträchtigt wird:Diese Regel ist bei AAPT2 völlig redundant. Da ein Platzhalter verwendet wird, ohne zu prüfen, ob die Komponente in der endgültigen zusammengeführten AndroidManifest.xml registriert ist, wird R8 gezwungen, jede Activity-, Service- oder BroadcastReceiver-Unterklasse beizubehalten, die irgendwo im Klassenpfad gefunden wird, einschließlich nicht verwendeter Bibliothekskomponenten und deaktivierter Debug-Aktivitäten.
  • Empfohlene Lösung:Löschen Sie die Regel. AAPT2 prüft das zusammengeführte Manifest und generiert genaue -keep-Regeln für registrierte Komponenten.

Allgemeine XML-Klick-Handler-Beibehaltungsregeln

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Warum die Leistung beeinträchtigt wird:Wenn jede public void *(View)-Methode in jeder Klasse im Modul beibehalten wird, kann R8 keine Methode entfernen oder inline einfügen, die einen einzelnen View-Parameter akzeptiert.
  • Empfohlene Lösung:Löschen Sie die Regel. AAPT2 scannt Layout-XML-Dateien und generiert gezielte Beibehaltungsregeln für Methoden, auf die durch android:onClick-Attribute verwiesen wird.

Allgemeine Getter- und Setter-Keep-Regeln für Ansichten

# 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*();
}
  • Warum die Leistung beeinträchtigt wird:Diese Regel zwingt R8, alle Getter und Setter für jede View-Unterklasse in der App und allen verknüpften Bibliotheken (einschließlich AndroidX- und Material-Bibliotheken) beizubehalten. Dadurch wird das Entfernen von ungenutzten Methoden und das Inlining im UI-Code blockiert.
  • Empfohlene Lösung:Löschen Sie die Regel. AAPT2 behält bereits Konstruktoren für benutzerdefinierte View-Klassen bei, die aus Layout-XML-Dateien generiert wurden. Wenn im Code eine Ansichtseigenschaft mit reflektierenden Stringnamen wie ObjectAnimator.ofFloat(view, "translationZ", ...) animiert wird, ersetzen Sie den Stringnamen durch einen typisierten Eigenschaftsverweis (View.TRANSLATION_Z oder eine benutzerdefinierte FloatProperty- oder IntProperty-Implementierung), um die Reflexion vollständig zu vermeiden. Wenn sich der Zugriff auf reflektierende Properties nicht vermeiden lässt, kommentieren Sie den spezifischen Getter oder Setter mit @UsedByReflection.

Pauschale Optimierung oder Verschleierung deaktiviert

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Warum sich das negativ auf die Leistung auswirkt:Wenn Sie -dontoptimize oder -dontshrink in einer .flags-Datei platzieren, werden die Android.bp-Einstellungen des Moduls ohne Benachrichtigung überschrieben und Optimierungsdurchläufe für das gesamte Ziel deaktiviert. Noch schlimmer: Wenn eine Bibliothek eine .flags-Datei mit -dontoptimize exportiert, wird die R8-Optimierung für jede nachgelagerte android_app deaktiviert, die die Bibliothek verknüpft.
  • Empfohlene Korrektur: Entfernen Sie -dontoptimize, -dontshrink und -dontobfuscate aus .flags-Dateien. Steuern Sie das Optimierungsverhalten explizit in Android.bp mit dem Block optimize (shrink, optimize, obfuscate) auf dem Blattziel.

Diagnose-Flags in übernommenen Regeln

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Warum es den Build beeinträchtigt: Diagnose-Flags überfluten Build-Logs oder versuchen, Ausgabedateien während Sandbox-Soong-Builds in lokale Pfade zu schreiben.
  • Empfohlene Lösung: Löschen Sie diese Flags aus den festgeschriebenen .flags-Dateien. Soong schreibt automatisch R8-Zuordnungs- und Nutzungsausgaben wie proguard_dictionary und proguard_usage.zip in das Zwischenverzeichnis des Moduls unter out/soong/.intermediates/.

Produktionsaufbewahrungsregeln für Testcode

  • Warum das die Leistung beeinträchtigt:Wenn du benutzerdefinierte -keep-Regeln nur für den Testzugriff hinzufügst, muss dieser Code unverschleiert bleiben und in Produktions-Builds beibehalten werden. Wenn Sie trace_references_from (oder allow_traced_references mit trace_references: true) konfigurieren oder Methoden mit @VisibleForTesting annotieren, müssen Sie keine manuellen -keep-Regeln verwalten und nur das beibehalten, worauf sich Tests beziehen. Diese Symbole werden jedoch weiterhin in der Produktionsbinärdatei bereitgestellt.
  • Empfohlene Lösung:Entfernen Sie benutzerdefinierte Test--keep-Regeln, indem Sie trace_references_from (oder allow_traced_references und trace_references: true) aktivieren, oder strukturieren Sie die App in eine .impl-Bibliothek, die mit static_libs mit einem selbstinstrumentierenden Test-APK verknüpft ist, um reine Testeinstiegspunkte vollständig aus dem Produktions-APK herauszuhalten, wie in Schritt 2: Testgesteuerte Beibehaltungsregeln migrieren beschrieben.

Alles einschließende Platzhalter für Ressourcen 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/*" />
  • Warum die Leistung beeinträchtigt wird: Pauschale Platzhalter behalten jede Ressource des übereinstimmenden Typs im gesamten Modul und seinen Abhängigkeiten bei. Dadurch wird die Entfernung von Ressourcen (shrink_resources: true) verhindert und das APK sowie die zugeordnete resources.arsc-Tabelle werden aufgebläht.
  • Empfohlene Lösung:
    • Statische Ressourcenreferenzen gegenüber keep.xml bevorzugen: Vermeide die Verwendung der Methode Resources.getIdentifier, um eine begrenzte Anzahl von Ressourcen dynamisch zu suchen, z. B. nummerierte Teststrings oder thematische Drawables. Verwenden Sie stattdessen eine switch-Anweisung zur Kompilierzeit oder ordnen Sie statische R.id-, R.string- oder R.drawable-Konstanten zu. Mit statischen Verweisen können R8 und AAPT2 die genauen Live-Ressourcen verfolgen, den Overhead für die Stringsuche zur Laufzeit eliminieren und die Notwendigkeit von keep.xml vollständig beseitigen.
    • Bestimmte Ressourcennamen auflisten: Wenn eine dynamische Suche erforderlich ist, z. B. durch einen Drittanbieter-Lizenzleser, listen Sie die genauen Ressourcenkennungen in tools:keep auf (z. B. tools:keep="@raw/third_party_licenses").
    • Redundante keep.xml-Dateien löschen: Wenn die aufgeführten Ressourcen bereits statisch im Code (R.raw.foo) oder XML (@raw/foo) referenziert sind, werden sie vom Shrinker automatisch beibehalten, sodass Sie res/raw/keep.xml löschen können.

Regeländerungen prüfen und auditieren

Wenn Sie Aufbewahrungsregeln in proguard.flags oder keep.xml entfernen oder einschränken, prüfen Sie, ob das Modul fehlerfrei erstellt wird, Unit- und Instrumentierungstests besteht und alle erforderlichen Einstiegspunkte beibehält.

Module erstellen und testen

  1. Erstelle das Modul sauber, um zu prüfen, ob R8 und AAPT2 ohne Warnungen zu fehlenden Referenzen abgeschlossen werden:

    m <MODULE_NAME>
    
  2. Führen Sie mit atest Unit- und Instrumentierungstests für das Modul aus:

    atest <TEST_MODULE_NAME>
    
  3. Führe für System-Apps, privilegierte Dienste oder hardwareabhängige Module Instrumentierungs- und UI-Tests auf physischen Zielgeräten oder in deinem Gerätetestlabor aus, um Laufzeitreflexionspfade, IPC-Bindungen und Ressourceninflation zu testen.

DEX- und Ressourcendifferenzen prüfen

Vergleicht das kompilierte APK oder JAR vor und nach den Regeländerungen, um zu prüfen, ob R8 ungenutzten Code und Ressourcen entfernt, ohne erwartete Einstiegspunkte zu entfernen:

  • Verwenden Sie apkanalyzer oder dexdump, um beibehaltene Klassen, Methoden und Felder in den Ausgabe-APK- oder DEX-Dateien zu prüfen:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Wenn du keep.xml änderst oder shrink_resources aktivierst, kannst du mit aapt2 dump resources prüfen, ob im Build nicht verwendete Ressourcen entfernt und erforderliche Ressourcen beibehalten werden:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Wenn Sie prebuilts/r8/r8.jar direkt auf der classes.jar-Datei eines Moduls vor R8 ausführen, um Flag-Änderungen offline ohne einen vollständigen Plattform-Build zu prüfen, übergeben Sie sowohl die --lib-Klassenpfad-JARs des Moduls (android.jar, framework.jar und freigegebene libs) als auch die von AAPT2 generierten proguard.options-Regeln aus AndroidManifest.xml und Layout-XMLs. Ohne diese Eingaben kann R8 keine Framework-Supertypen (Activity, Service, Phone, Handler) auflösen und behält nicht genügend Hierarchiemethoden bei.

Keep-Radius und Regel-Subsumption mit R8 analysieren

Der Open-Source-R8-Compiler enthält einen Keep Radius-Analysator (in Android Studio und Gradle auch als R8 Configuration Analyzer verfügbar), der die genaue Auswirkung jeder Keep-Regel während der Kompilierung misst. Soong integriert diesen Analyzer direkt in den Build der Android-Plattform (konfiguriert in build/soong/java/dex.go).

So analysieren Sie Beibehaltungsregeln für ein Plattformmodul oder einen gesamten Build:

  1. Führen Sie den Build mit der Umgebungsvariablen R8_DUMP_KEEP_RADIUS=true aus:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    Wenn R8_DUMP_KEEP_RADIUS=true festgelegt ist, weist Soong R8 an, Messwerte für Beibehaltungsregeln in einer Zwischendatei r8keepradius.pb für jedes kompilierte Modul unter out/soong/.intermediates/ aufzuzeichnen. Für jede Beibehaltungsregel und keepanno-Annotation zeichnet R8 Folgendes auf:

    • Unmittelbarer Beibehaltungsradius: Die genauen Klassen, Felder und Methoden, die durch die Regel beibehalten werden, sowie die spezifischen Einschränkungen, die sie für die Verkleinerung, Optimierung oder Verschleierung erzwingt.
    • Regelsubsumtion:Welche anderen Aufbewahrungsregeln oder Annotationen bereits dieselben Elemente aufbewahren. Wenn eine benutzerdefinierte Regel vollständig von einer AAPT2-Regel oder einer globalen Baseline-Regel abgedeckt wird, können Sie sie bedenkenlos löschen.
    • Paketweite und globale Regeln: Regeln, die allgemeine paketweite Platzhalter verwenden oder globale Konfigurationsanweisungen anwenden.
  2. Konvertieren Sie die r8keepradius.pb-Ausgabe mit KeepRadiusHtmlReportGenerator (in prebuilts/r8/r8.jar enthalten) in einen interaktiven HTML-Bericht:

    # 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
    

    Wenn Sie ein Verzeichnis angeben, durchsucht KeepRadiusHtmlReportGenerator alle *keepradius*.pb-Dateien, generiert für jedes Modul einen HTML-Bericht und erstellt out/keep_radius_reports/keepradius.html, in dem die Anzahl der Live-Elemente, die Anzahl der beibehaltenen Elemente und die Beibehaltungsregeln mit dem größten Radius im gesamten Build zusammengefasst werden. Weitere Informationen zur Interpretation der Werte für Verkleinerung, Optimierung und Obfuskation im generierten Bericht finden Sie unter R8 Configuration Analyzer verwenden.

Soong unterstützt auch zusätzliche R8-Diagnose- und Inner-Loop-Umgebungsvariablen in build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: Schreibt r8inputs.zip in das Zwischenverzeichnis des Moduls, das alle Eingabe-JARs, Bibliotheks-JARs und zusammengeführten ProGuard-Konfigurationen für die eigenständige R8-Reproduktion enthält.
  • R8_DUMP_PERFETTO_TRACE=true: Schreibt r8trace.ptrace in das Zwischenverzeichnis des Moduls, um R8-Kompilierungsdurchläufe in Perfetto zu prüfen.
  • SOONG_SKIP_TRACE_REFERENCES=true: Überspringt die Ausführung von tracereferences-Regeln, die von trace_references_from oder trace_references konfiguriert wurden, um lokale Inner-Loop-Builds beim Iterieren von Testcode zu beschleunigen.

Nutzerregeln für die Bibliothek validieren

Wenn ein java_library oder android_library Aufbewahrungsregeln an nachgelagerte Nutzer (export_proguard_flags_files: true) exportiert, dürfen diese Regeln keine globalen Flags enthalten, die die Optimierung für die nutzende App ändern oder deaktivieren.

R8 bietet einen Open-Source-Keep-Rule-Analyzer (ProcessKeepRules), der im Android-Plattform-Build als Host-Tool process-keep-rules (definiert in prebuilts/r8/Android.bp) bereitgestellt wird:

# 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>

Das process-keep-rules-Tool parst jede Konfigurationsdatei und schlägt mit Datei- und Zeilendiagnosen fehl, wenn es auf Anweisungen stößt, die in den Regeln für Bibliotheksnutzer nicht zulässig sind. Zu den unzulässigen Anweisungen gehören die Deaktivierung der globalen Optimierung, der Verkleinerung oder der Verschleierung (z. B. -dontoptimize oder -dontshrink), Flags für das Umpacken von Paketen und die Zugriffsänderung, Flags für die Diagnose oder Zuordnung sowie -keepattributes auf App-Ebene.

Quellbäume mit pgaudit.py prüfen

Um nicht erstellte Quellverzeichnisse zusammen mit den Build-Zeit-Analysetools von R8 zu prüfen, enthält der Android-Plattformbaum das Skript pgaudit.py unter build/make/core/proguard/tools/. Dieses statische Analysetool scannt Android.bp-, proguard.flags- und keep.xml-Dateien in einem Repository, um Regeln zu kennzeichnen, die bereits von AAPT2 oder globalen Plattform-Baselines abgedeckt sind, sowie allgemeine Platzhalter, -dontoptimize-Überschreibungen und Regeln, die nur für Tests gelten:

# 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

Plattform- und OEM-Teams können pgaudit.py für eine schnelle Quellbaum-Triage, process-keep-rules zur Validierung exportierter Bibliotheksregeln und R8_DUMP_KEEP_RADIUS=true mit KeepRadiusHtmlReportGenerator kombinieren, um den genauen Aufbewahrungsradius für Klassen, Felder und Methoden jeder Regel in einem Build zu messen.