Produktneuigkeiten

R8-Keep-Regeln konfigurieren und Fehler beheben

Lesezeit: 7 Minuten
Profil von Ajesh Pai ansehen Profil von Ben Weiss ansehen
Ajesh Pai und Ben Weiss

In der modernen Android-Entwicklung ist die Bereitstellung einer kleinen, schnellen und sicheren Anwendung eine grundlegende Erwartung der Nutzer. Das wichtigste Tool des Android-Build-Systems ist der R8-Optimierer . Dieser Compiler entfernt ungenutzten Code und ungenutzte Ressourcen, um die App zu verkleinern, benennt Code um oder minimiert ihn und optimiert die App.

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

Sehen Sie sich nach dem Lesen dieses Artikels das Video der Performance Spotlight Week zum Aktivieren, Debuggen und Beheben von Fehlern des R8-Optimierers auf YouTube an.

 

 

Warum sind Aufbewahrungsregeln erforderlich?

Die Notwendigkeit, Keep-Regeln zu schreiben, ergibt sich aus einem grundlegenden Konflikt: R8 ist ein Tool für die statische Analyse, aber Android-Apps basieren oft auf dynamischen Ausführungsmustern wie Reflexion oder Aufrufen in und aus nativem Code über das 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 dies nicht vorhersagen. Der Code wird als nicht verwendet identifiziert und entfernt, was zu Abstürzen während der Laufzeit führt.

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

Weitere Informationen zu Aufbewahrungsregeln finden Sie im offiziellen Leitfaden.

Wo werden Aufbewahrungsregeln erstellt?

Benutzerdefinierte Behaltensregeln für eine Anwendung werden in eine 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",

    )

}

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, wird 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-Datei enthält nur die Beibehaltungsregeln, aktiviert aber nicht die Optimierungen von R8.

progaurd.png

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

Beibehalten-Regeln 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 Aufbewahrungsregeln

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

Globale Optionen

Diese Flags sind globale Schalter, die niemals in einem Release-Build verwendet werden sollten. Sie dienen nur zur vorübergehenden Fehlerbehebung, um ein Problem einzugrenzen.

Wenn Sie -dontotptimize verwenden, werden die Leistungsoptimierungen von R8 deaktiviert, was zu einer langsameren App führt.

Wenn Sie -dontobfuscate verwenden, werden alle Umbenennungen deaktiviert. Mit -dontshrink wird das Entfernen von totem Code deaktiviert. Beide globalen Regeln erhöhen die App-Größe.

Vermeiden Sie diese globalen Flags in einer Produktionsumgebung, um die Leistung der Anwendung zu verbessern.

Zu weit gefasste Behaltensregeln

Am einfachsten lassen sich die Vorteile von R8 zunichtemachen, indem Sie zu weit gefasste Keep-Regeln schreiben. Keep-Regeln wie die folgende weisen den R8-Optimierer an, keine Klasse in diesem Paket oder in einem 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, eng gefasste und spezifische Aufbewahrungsregeln zu erstellen.
 

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

Der Inversionsoperator (!)

Der Inversionsoperator (!) scheint eine gute Möglichkeit zu sein, ein Paket von einer Regel auszuschließen. Aber so einfach ist das nicht. Schauen wir uns dieses Beispiel an:

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

Sie denken vielleicht, dass diese Regel „Klassen in com.example.package nicht beibehalten“ bedeutet. Tatsächlich bedeutet sie aber „Alle Klassen, Methoden und Properties in der gesamten Anwendung beibehalten, die nicht in com.example.package sind“. 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 Beibehaltungsregeln für 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 bringen auch eigene Beibehaltungsregeln mit. Sie müssen also keine eigenen Regeln dafür schreiben. Wenn ein Problem mit den Beibehaltungsregeln einer von Ihnen verwendeten Bibliothek auftritt, wenden Sie sich am besten an den Autor der Bibliothek, um das Problem zu ermitteln.

Best Practices für Aufbewahrungsregeln

Sie wissen nun, was Sie vermeiden sollten. Kommen wir jetzt zu den Best Practices.

Spezifische Aufbewahrungsregeln erstellen

Gute Aufbewahrungsregeln sollten so eng gefasst 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: Ein ganzes Paket und seine Unterpakete werden beibehalten.

 

-keep class com.example.MyClass { ; }

 

Niedrig:Behält eine ganze Klasse bei, was wahrscheinlich immer 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 Properties einer bestimmten Klasse werden beibehalten.

Gemeinsame Ancestor verwenden

Anstatt separate Beibehaltungsregeln für mehrere verschiedene Datenmodelle zu schreiben, schreiben Sie eine Regel, die auf eine gemeinsame Basisklasse oder Schnittstelle abzielt. Die folgende Regel weist R8 an, alle Mitglieder von Klassen beizubehalten, die diese Schnittstelle implementieren, und ist hochgradig skalierbar.

# Keep all fields of any class that implements SerializableModel

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

    <fields>;

}

Annotationen verwenden, um mehrere Klassen anzusprechen

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

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

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

}

Die richtige Option zum Beibehalten auswählen

Die Option „Beibehalten“ ist der wichtigste Teil der Regel. Wenn Sie die falsche Option auswählen, wird die Optimierung möglicherweise unnötigerweise deaktiviert.

Option „Beibehalten“Funktion
-keepVerhindert, dass die Klasse und die in der Deklaration genannten Mitglieder  entfernt oder umbenannt werden.
-keepclassmembersVerhindert, dass die angegebenen Mitglieder entfernt oder umbenannt werden. Die Klasse selbst kann jedoch entfernt werden, allerdings nur bei Klassen, die nicht anderweitig entfernt werden.
-keepclasseswithmembersEine Kombination: Die Klasse und ihre Klassenmitglieder werden beibehalten, aber nur, wenn alle angegebenen Klassenmitglieder vorhanden sind.

Weitere Informationen zur Option „Beibehalten“ finden Sie in unserer Dokumentation zu den Optionen für „Beibehalten“.

Optimierung mit Modifikatoren zulassen

Modifikatoren wie allowshrinking und allowobfuscation lockern eine allgemeine -keep-Regel und geben die Optimierungsmöglichkeit an R8 zurück. Wenn Sie beispielsweise aufgrund einer Legacy-Bibliothek -keep für eine ganze Klasse verwenden müssen, können Sie möglicherweise einen Teil der Optimierung zurückgewinnen, indem Sie die Minimierung und Verschleierung 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, die R8 anweist, alle verschleierten Klassen in ein einzelnes Paket zu verschieben. Dadurch wird in der DEX-Datei erheblich Platz gespart, da redundante Paketnamenstrings entfernt werden.

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

Warnung:Autoren von Bibliotheken dürfen diese globalen Optimierungs-Flags niemals ihren Nutzerregeln hinzufügen, da sie dann zwangsweise auf die gesamte App angewendet werden.

Um das noch deutlicher zu machen, werden wir in Version 9.0 des Android-Gradle-Plug-ins globale Optimierungs-Flags aus Bibliotheken vollständig ignorieren. 

Best Practices für Bibliotheken

Jede Android-App ist auf die eine oder andere Weise auf Bibliotheken angewiesen. Sehen wir uns also Best Practices für Bibliotheken an.

Für Entwickler von Bibliotheken

Wenn Ihre Bibliothek Reflection oder JNI verwendet, müssen Sie den Nutzern die erforderlichen Keep-Regeln zur Verfügung stellen. Diese Regeln werden in einer consumer-rules.pro-Datei abgelegt, 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 mit problematischen Keep-Regeln verwenden müssen, können Sie diese ab AGP 9.0 in Ihrer build.gradle.kts-Datei herausfiltern. Dadurch wird R8 angewiesen, die Regeln zu ignorieren, die von einer bestimmten Abhängigkeit stammen.

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

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

    }

}

Die beste Regel für Notizen ist, keine Regel zu haben

Die ultimative R8-Konfigurationsstrategie besteht darin, das Schreiben von Keep-Regeln vollständig zu vermeiden. Bei vielen Apps lässt sich das erreichen, indem moderne Bibliotheken ausgewählt werden, die die Codegenerierung gegenüber der Reflexion bevorzugen. Mit der Codegenerierung kann der Optimierer leichter feststellen, welcher Code tatsächlich zur Laufzeit verwendet wird und welcher Code entfernt werden kann. Da keine dynamische Reflexion verwendet wird, gibt es keine „versteckten“ Einstiegspunkte und daher sind keine Keep-Regeln erforderlich. Wenn Sie eine neue Bibliothek auswählen, sollten Sie immer eine Lösung bevorzugen, die Codegenerierung anstelle von Reflexion verwendet.

Weitere Informationen zur Auswahl von Bibliotheken finden Sie unter Bibliothek sorgfältig auswählen.

Debugging und Fehlerbehebung für Ihre R8-Konfiguration

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

Doppelte und globale Aufbewahrungsregeln finden

Da R8 Regeln aus Dutzenden von Quellen zusammenführt, kann es schwierig sein, den „endgültigen“ Regelsatz zu ermitteln. Wenn Sie dieses Flag zu Ihrer proguard-rules.pro-Datei hinzufü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 diese Datei durchsuchen, um redundante Regeln zu finden oder eine problematische Regel (z. B. -dontoptimize) auf die spezifische Bibliothek zurückzuführen, die sie enthält.

Frage an R8: Warum behältst du das?

Wenn eine Klasse, die eigentlich entfernt werden sollte, noch 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 Verweisen aus, die dazu geführt hat, dass die Klasse beibehalten wurde. So können Sie den Verweis nachverfolgen und Ihre Regeln anpassen.

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

Weiteres Vorgehen

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

Durch das Schreiben spezifischer Regeln auf Mitgliedsebene, die Verwendung von Ancestors und Annotationen sowie die sorgfältige Auswahl der richtigen Aufbewahrungsoptionen können Sie genau das aufbewahren, was erforderlich ist. Die fortschrittlichste Methode besteht darin, die Notwendigkeit von Regeln vollständig zu eliminieren, indem Sie moderne, codegenerierungsbasierte Bibliotheken anstelle ihrer reflexionsbasierten Vorgänger wählen.

Sehen Sie sich im Rahmen der Performance Spotlight Week das heutige Spotlight Week-Video auf YouTube an und fahren Sie mit unserer R8-Challenge fort. Verwenden Sie #optimizationEnabled für alle Fragen zur Aktivierung oder Fehlerbehebung von R8. Wir sind für Sie da.

Überzeuge dich selbst von den Vorteilen.

Wir fordern Sie auf, den vollständigen R8-Modus für Ihre App noch heute zu aktivieren.

  1. Folgen Sie unseren Entwicklerleitfäden, 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. Du kannst den Unterschied nicht nur spüren, sondern auch sehen. Du kannst die Leistungssteigerung messen, indem du den Code aus unserer  Macrobenchmark-Beispiel-App auf GitHub anpasst, um die Startzeiten vor und nach der Optimierung zu messen.

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

Verwende dabei das Social-Media-Tag #AskAndroid, um deine Fragen zu stellen. Unsere Experten sehen sich die Fragen regelmäßig an und beantworten sie.

Morgen geht es um die profilgesteuerte Optimierung mit Baseline- und Startprofilen, die Verbesserung der Compose-Rendering-Leistung in den letzten Releases und Leistungsaspekte für Hintergrundaufgaben.

Verfasst von:
Weiterlesen