Produktneuheiten

R8-Keep-Regeln konfigurieren und Fehler beheben

Lesezeit: 7 Minuten

In der modernen Android-Entwicklung ist es eine grundlegende Erwartung der Nutzer, dass Apps klein, schnell und sicher sind. Das primäre Tool des Android-Build-Systems, um dies zu erreichen, ist der R8-Optimierer . Dieser Compiler kümmert sich um das Entfernen von nicht verwendetem Code und Ressourcen zum Verkleinern, Umbenennen oder Minimieren von Code sowie um die App-Optimierung.

Die Aktivierung von R8 ist ein wichtiger Schritt bei der Vorbereitung einer App für die Veröffentlichung. Entwickler müssen jedoch Anleitungen in Form von „Keep-Regeln“ bereitstellen.

Nachdem Sie diesen Artikel gelesen haben, können Sie sich das Video zur Performance Spotlight Week auf YouTube ansehen, in dem es um das Aktivieren, Debuggen und Beheben von Problemen mit dem R8-Optimierer geht.

 

 

Warum sind Aufbewahrungsregeln erforderlich?

Die Notwendigkeit, Keep-Regeln zu schreiben, ergibt sich aus einem grundlegenden Konflikt: R8 ist ein Tool zur statischen Analyse, Android-Apps basieren jedoch häufig auf dynamischen Ausführungsmustern wie Reflection oder Aufrufen in und aus nativem Code über die JNI (Java Native Interface).

R8 erstellt ein Diagramm des verwendeten Codes, indem direkte Aufrufe analysiert werden. Wenn auf Code dynamisch zugegriffen wird, kann die statische Analyse von R8 das nicht vorhersagen. Der Code wird als nicht verwendet identifiziert und entfernt, was zu Laufzeitabstürzen führt.

Eine Keep-Regel ist eine explizite Anweisung an den R8-Compiler, die besagt: „Diese bestimmte Klasse, Methode oder dieses bestimmte Feld ist ein Einstiegspunkt, auf den zur Laufzeit dynamisch zugegriffen wird. Sie müssen sie beibehalten, auch wenn Sie keinen direkten Verweis darauf finden.“

Weitere Informationen zu Aufbewahrungsregeln finden Sie im offiziellen Leitfaden.

Regeln für Google Notizen schreiben

Benutzerdefinierte Keep-Regeln für eine Anwendung werden in einer Textdatei geschrieben. Konventionsgemäß heißt diese Datei proguard-rules.pro und befindet sich im Stammverzeichnis des App- oder Bibliotheksmoduls. Diese Datei wird dann im Build-Typ release der Datei build.gradle.kts Ihres Moduls angegeben.

  release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

Die richtige Standarddatei verwenden

Mit der Methode getDefaultProguardFile wird ein Standardsatz von Regeln importiert, die vom Android SDK bereitgestellt werden. Wenn Sie die falsche Datei verwenden, ist Ihre App möglicherweise nicht optimiert. Verwenden Sie proguard-android-optimize.txt. Diese Datei enthält die Standard-Keep-Regeln für Standard-Android-Komponenten und aktiviert die Codeoptimierungen von R8. Die veraltete proguard-android.txt bietet nur die Keep-Regeln, nicht aber die Optimierungen von R8.

progaurd.png

Da dies ein schwerwiegendes Leistungsproblem ist, warnen wir Entwickler ab dem Android Studio Narwhal 3 Feature Drop vor der Verwendung der falschen Datei. Ab Android-Gradle-Plug-in 9.0 wird die veraltete proguard-android.txt-Datei nicht mehr unterstützt. Führen Sie deshalb ein Upgrade auf die optimierte Version durch.

Regeln zum Beibehalten von Daten schreiben

Eine Aufbewahrungsregel besteht aus drei Hauptteilen:

  1. Eine Option  wie -keep oder -keepclassmembers
  2. Optionale Modifikatoren wie allowshrinking
  3. Eine Klassenspezifikation, die den abzugleichenden Code definiert

Die vollständige Syntax und Beispiele finden Sie in der Anleitung zum Hinzufügen von Aufbewahrungsregeln.

Anti-Muster für „Keep“-Regeln

Es ist wichtig, Best Practices, aber auch Anti-Patterns zu kennen. Diese Anti-Patterns entstehen oft durch Missverständnisse oder Abkürzungen bei der Fehlerbehebung und können sich katastrophal auf die Leistung eines Produktions-Builds auswirken.

Globale Optionen

Diese Flags sind globale Schalter, die niemals in einem Release-Build verwendet werden sollten. Sie sind nur für das vorübergehende Debugging vorgesehen, um ein Problem einzugrenzen.

Durch die Verwendung von -dontotptimize werden die Leistungsoptimierungen von R8 effektiv deaktiviert, was zu einer langsameren App führt.

Wenn Sie -dontobfuscate verwenden, deaktivieren Sie das Umbenennen. Wenn Sie -dontshrink verwenden, wird das Entfernen von nicht verwendetem Code deaktiviert. Beide globalen Regeln erhöhen die App-Größe.

Vermeiden Sie die Verwendung dieser globalen Flags in einer Produktionsumgebung, um die Leistung der App zu verbessern.

Zu allgemeine Aufbewahrungsregeln

Am einfachsten lassen sich die Vorteile von R8 zunichtemachen, indem Sie zu weit gefasste Keep-Regeln schreiben. Mit Regeln wie der unten stehenden wird der R8-Optimierer angewiesen, keine Klasse in diesem Paket oder keines seiner Unterpakete zu verkleinern, zu verschleiern oder zu optimieren. Dadurch werden die Vorteile von R8 für das gesamte Paket vollständig entfernt. Versuchen Sie stattdessen, enge und spezifische Aufbewahrungsregeln zu schreiben.
 

  -keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

Der Inversionsoperator (!)

Der Inversionsoperator (!) ist eine leistungsstarke Methode, um ein Paket aus einer Regel auszuschließen. So einfach ist es aber nicht. Sehen wir uns dieses Beispiel an:

  -keep class !com.example.my_package.** { *; } // USE WITH CAUTION

Sie denken vielleicht, dass diese Regel bedeutet: Klassen in com.example.package nicht beibehalten . Sie bedeutet aber: Jede Klasse, Methode und Eigenschaft in der gesamten Anwendung, die nicht in com.example.package enthalten ist, beibehalten. Wenn Sie das überrascht, sollten Sie Ihre R8-Konfiguration auf Negationen prüfen.

Redundante Regeln für Android-Komponenten

Ein weiterer häufiger Fehler ist das manuelle Hinzufügen von Keep-Regeln für die Activities, Services oder BroadcastReceivers Ihrer App. Das ist nicht erforderlich. Die Standarddatei proguard-android-optimize.txt enthält bereits die relevanten Regeln, damit diese Standard-Android-Komponenten sofort funktionieren.

Viele Bibliotheken haben auch eigene Aufbewahrungsregeln. Sie müssen also keine eigenen Regeln dafür schreiben. Wenn es ein Problem mit Keep-Regeln aus einer Bibliothek gibt, die Sie verwenden, wenden Sie sich am besten an den Autor der Bibliothek, um das Problem zu ermitteln.

Best Practices für Regeln

Nachdem Sie nun wissen, was Sie nicht tun sollten, sprechen wir über Best Practices.

Eingrenzte Regeln für Google Notizen schreiben

Gute Aufbewahrungsregeln sollten so eng und spezifisch wie möglich sein. Sie sollten nur das beibehalten, was notwendig ist, damit R8 alles andere optimieren kann.
 

RegelQualität

 

-keep class com.example.** { ; }

 

Niedrig:Behält ein gesamtes Paket und seine Unterpakete bei.

 

-keep class com.example.MyClass { ; }

 

Niedrig:Behält eine ganze Klasse bei, die wahrscheinlich noch zu breit ist.
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
Hoch:Nur relevante Methoden und Eigenschaften einer bestimmten Klasse werden beibehalten.

Gemeinsame Vorfahren verwenden

Anstatt separate Keep-Regeln für mehrere verschiedene Datenmodelle zu schreiben, sollten Sie eine Regel schreiben, die auf eine gemeinsame Basisklasse oder ‑schnittstelle ausgerichtet ist. Die folgende Regel weist R8 an, alle Elemente von Klassen beizubehalten, die diese Schnittstelle implementieren. Sie ist sehr skalierbar.

  # Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

Mit Anmerkungen mehrere Klassen ansprechen

Erstellen Sie eine benutzerdefinierte Annotation (z.B. @Serialize) und verwenden Sie sie, um Klassen zu „taggen“, deren Felder beibehalten werden müssen. Dies ist ein weiteres übersichtliches, deklaratives und hochgradig skalierbares Muster. Sie können auch Keep-Regeln für bereits vorhandene Anmerkungen aus Frameworks erstellen, die Sie verwenden.

  # Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

Die richtige Keep-Option auswählen

Die Option „Behalten“ ist der wichtigste Teil der Regel. Wenn Sie den falschen auswählen, kann die Optimierung unnötigerweise deaktiviert werden.

Option „Beibehalten“Funktionsweise
-keepVerhindert, dass die Klasse und die in der Deklaration erwähnten Mitglieder  entfernt oder umbenannt werden.
-keepclassmembersVerhindert, dass die angegebenen Mitglieder entfernt oder umbenannt werden. Die Klasse selbst kann jedoch entfernt werden, aber nur bei Klassen, die nicht anderweitig entfernt werden.
-keepclasseswithmembersKombination: Die Klasse und ihre Elemente werden beibehalten, aber nur, wenn alle angegebenen Elemente vorhanden sind.

Weitere Informationen zur Option „Beibehalten“ finden Sie in der Dokumentation zu den Beibehalten-Optionen.

Optimierung mit Modifikatoren zulassen

Modifikatoren wie allowshrinking und allowobfuscation lockern eine allgemeine -keep-Regel und geben R8 die Möglichkeit zur Optimierung zurück. Wenn Sie beispielsweise aufgrund einer alten Bibliothek -keep für eine ganze Klasse verwenden müssen, können Sie möglicherweise einige Optimierungen zurückgewinnen, indem Sie das Verkleinern und Verschleiern zulassen:

  # Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

Globale Optionen für zusätzliche Optimierung hinzufügen

Neben Keep-Regeln können Sie Ihrer R8-Konfigurationsdatei globale Flags hinzufügen, um die Optimierung weiter zu verbessern.

-repackageclasses ist eine leistungsstarke Option, mit der R8 angewiesen wird, alle verschleierten Klassen in ein einzelnes Paket zu verschieben. Dadurch wird in der DEX-Datei viel Speicherplatz gespart, da redundante Paketnamensstrings entfernt werden.

-allowaccessmodification ermöglicht R8, den Zugriff zu erweitern (z.B. von private zu public), um aggressiveres Inlining zu ermöglichen. Diese Funktion ist jetzt standardmäßig aktiviert, wenn Sie proguard-android-optimize.txt verwenden.

Warnung:Bibliotheksautoren dürfen diese globalen Optimierungsflags niemals ihren Consumer-Regeln hinzufügen, da sie sonst auf die gesamte App angewendet würden.

Um das noch deutlicher zu machen, werden in Version 9.0 des Android-Gradle-Plug-ins globale Optimierungsflags aus Bibliotheken ignoriert. 

Best Practices für Bibliotheken

Jede Android-App verwendet Bibliotheken. Sehen wir uns also Best Practices für Bibliotheken an.

Für Entwickler von Bibliotheken

Wenn in Ihrer Bibliothek Reflection oder JNI verwendet wird, müssen Sie den Nutzern die erforderlichen Keep-Regeln zur Verfügung stellen. Diese Regeln werden in einer consumer-rules.pro-Datei platziert, die dann automatisch in der AAR-Datei der Bibliothek gebündelt wird.

  android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

Für Nutzer der Bibliothek

Problematische Aufbewahrungsregeln herausfiltern

Wenn Sie eine Bibliothek verwenden müssen, die problematische Keep-Regeln enthält, können Sie diese ab AGP 9.0 in Ihrer build.gradle.kts-Datei herausfiltern. Dadurch wird R8 angewiesen, die Regeln einer bestimmten Abhängigkeit zu ignorieren.

  release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

Die beste Aufbewahrungsregel ist keine Aufbewahrungsregel

Die ultimative R8-Konfigurationsstrategie besteht darin, das Schreiben von Keep-Regeln vollständig zu vermeiden. Bei vielen Apps kann dies durch die Auswahl moderner Bibliotheken erreicht werden, die Codegenerierung gegenüber der Reflektion bevorzugen. Durch die Codegenerierung kann der Optimierer leichter ermitteln, welcher Code zur Laufzeit tatsächlich verwendet wird und welcher Code entfernt werden kann. Da keine dynamische Reflektion verwendet wird, gibt es auch keine „versteckten“ Einstiegspunkte. Daher sind keine Keep-Regeln erforderlich. Wenn Sie eine neue Bibliothek auswählen, sollten Sie immer eine Lösung bevorzugen, die Codegenerierung anstelle von Reflection verwendet.

Weitere Informationen zum Auswählen von Bibliotheken finden Sie unter Bibliothek sorgfältig auswählen.

Debugging und Fehlerbehebung bei der R8-Konfiguration

Wenn R8 Code entfernt, der hätte beibehalten werden sollen, oder Ihr APK größer als erwartet ist, können Sie das Problem mit diesen Tools diagnostizieren.

Doppelte und globale Aufbewahrungsregeln finden

Da R8 Regeln aus Dutzenden von Quellen zusammenführt, ist es schwierig, das „endgültige“ Regelset zu ermitteln. Wenn Sie dieses Flag in Ihre proguard-rules.pro-Datei einfügen, wird ein vollständiger Bericht generiert:

  # Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

Sie können in dieser Datei nach redundanten Regeln suchen oder eine problematische Regel (z. B. -dontoptimize) auf die spezifische Bibliothek zurückführen, die sie enthält.

Frage R8: Warum behältst du das?

Wenn ein Kurs, der eigentlich entfernt werden sollte, weiterhin in Ihrer App vorhanden ist, kann R8 Ihnen den Grund dafür nennen. Fügen Sie einfach diese Regel hinzu:

  # Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

Während des Builds gibt R8 die genaue Kette von Referenzen aus, die dazu geführt hat, dass die Klasse beibehalten wurde. So können Sie die Referenz nachvollziehen und Ihre Regeln anpassen.

Eine vollständige Anleitung finden Sie im Abschnitt Fehlerbehebung bei R8.

Nächste Schritte

R8 ist ein leistungsstarkes Tool zur Verbesserung der Leistung von Android-Apps. Die Effektivität hängt davon ab, dass die Funktionsweise als Engine für statische Analysen richtig verstanden wird.

Wenn Sie spezifische Regeln auf Mitgliedsebene schreiben, Vorfahren und Anmerkungen verwenden und die richtigen Aufbewahrungsoptionen sorgfältig auswählen, können Sie genau das aufbewahren, was erforderlich ist. Die fortschrittlichste Methode besteht darin, Regeln ganz zu vermeiden, indem Sie moderne, auf Codegenerierung basierende Bibliotheken anstelle ihrer auf Reflexion basierenden Vorgänger verwenden.

Sehen Sie sich das heutige Spotlight Week-Video auf YouTube an und machen Sie mit unserer R8-Challenge weiter. Verwenden Sie #optimizationEnabled für alle Fragen zur Aktivierung oder Fehlerbehebung von R8. Wir helfen dir gerne weiter.

Überzeugen Sie sich selbst von den Vorteilen.

Wir empfehlen Ihnen, den vollständigen R8-Modus für Ihre App noch heute zu aktivieren.

  1. Folgen Sie unserem Entwicklerleitfaden, um loszulegen: App-Optimierung aktivieren.
  2. Prüfen Sie, ob Sie noch proguard-android.txt verwenden, und ersetzen Sie es durch proguard-android-optimize.txt.
  3. Messen Sie dann die Auswirkungen. Spüren Sie nicht nur den Unterschied, sondern bestätigen Sie ihn. Messen Sie die Leistungssteigerungen, indem Sie den Code aus unserer  Macrobenchmark-Beispiel-App auf GitHub anpassen, um die Startzeiten vor und nach der Optimierung zu messen.

Wir sind zuversichtlich, dass sich die Leistung Ihrer App dadurch deutlich verbessern wird.

Nutze den Social-Media-Tag #AskAndroid, um deine Fragen zu stellen. Unsere Experten beantworten Ihre Fragen während der gesamten Woche.

Morgen geht es weiter mit Profiloptimierung mit Baseline- und Startup-Profilen. Außerdem erfahrt ihr, wie sich die Compose-Rendering-Leistung in den letzten Releases verbessert hat, und erhaltet Tipps zur Leistung bei Hintergrundaufgaben.

Verfasst von:

Weiterlesen