Grob gesagt gibt eine Beibehaltungsregel eine Klasse (oder Unterklasse oder Implementierung) und dann die Mitglieder (Methoden, Konstruktoren oder Felder) innerhalb dieser Klasse an, die beibehalten werden sollen.
Die allgemeine Syntax für eine Aufbewahrungsregel lautet wie folgt:
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Im Folgenden finden Sie ein Beispiel für eine Aufbewahrungsregel, die keepclassmembers als Aufbewahrungsoption und allowoptimization als Modifikator verwendet und someSpecificMethod() aus com.example.MyClass aufbewahrt:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Option „Beibehalten“
Die Aufbewahrungsoption ist der erste Teil Ihrer Aufbewahrungsregel. Sie gibt an, welche Aspekte einer Klasse beibehalten werden sollen. Es gibt sechs verschiedene Optionen zum Beibehalten: keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames und keepclasseswithmembernames.
In der folgenden Tabelle werden diese Optionen beschrieben:
| Option „Beibehalten“ | Beschreibung |
|---|---|
keepclassmembers |
Behält angegebene Elemente nur bei, wenn R8 die Klasse, die sie enthält, nicht entfernt. |
keep |
Die angegebenen Klassen und die angegebenen Elemente (Felder und Methoden) werden beibehalten und nicht optimiert. Hinweis: keep sollte im Allgemeinen nur mit Keep-Optionsmodifikatoren verwendet werden, da keep allein verhindert, dass Optimierungen jeglicher Art für übereinstimmende Klassen vorgenommen werden. |
keepclasseswithmembers |
Behält eine Klasse und ihre angegebenen Mitglieder nur bei, wenn die Klasse alle Mitglieder aus der Klassenspezifikation enthält. |
keepclassmembernames |
Verhindert das Umbenennen angegebener Klassenmember, aber nicht das Entfernen der Klasse oder ihrer Member. Hinweis: Die Bedeutung dieser Option wird oft missverstanden. Verwenden Sie stattdessen die entsprechende Option -keepclassmembers,allowshrinking. |
keepnames |
Verhindert das Umbenennen von Klassen und ihren Mitgliedern, aber nicht, dass sie vollständig entfernt werden, wenn sie als nicht verwendet gelten. Hinweis: Die Bedeutung dieser Option wird oft missverstanden. Verwenden Sie stattdessen die entsprechende Option -keep,allowshrinking. |
keepclasseswithmembernames |
Verhindert das Umbenennen von Klassen und ihren angegebenen Mitgliedern, aber nur, wenn die Mitglieder im endgültigen Code vorhanden sind. Das Entfernen von Code wird dadurch nicht verhindert. Hinweis: Die Bedeutung dieser Option wird oft missverstanden. Verwenden Sie stattdessen die entsprechende Option -keepclasseswithmembers,allowshrinking. |
Die richtige Option zum Beibehalten auswählen
Die Auswahl der richtigen Beibehaltungsoption ist entscheidend für die richtige Optimierung Ihrer App. Bei bestimmten Beibehaltungsoptionen wird der Code verkleinert, d. h., nicht referenzierter Code wird entfernt. Bei anderen wird der Code verschleiert oder umbenannt. In der folgenden Tabelle werden die Aktionen der verschiedenen Beibehalten-Optionen beschrieben:
| Option „Behalten“ | Klassen verkleinern | Klassen verschleiern | Mitglieder schrumpfen | Verschleiert Mitglieder |
|---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modifikator für die Option „Beibehalten“
Mit einem Modifikator für die Option „Beibehalten“ wird der Umfang und das Verhalten einer Keep-Regel gesteuert. Sie können Ihrer Aufbewahrungsregel keine oder mehrere Modifikatoren für Aufbewahrungsoptionen hinzufügen.
Die möglichen Werte für einen Modifier für die Beibehaltungsoption werden in der folgenden Tabelle beschrieben:
| Wert | Beschreibung |
|---|---|
allowoptimization |
Ermöglicht die Optimierung der angegebenen Elemente. Die angegebenen Elemente werden jedoch nicht umbenannt oder entfernt. |
allowobfuscation |
Ermöglicht das Umbenennen der angegebenen Elemente. Die Elemente werden jedoch nicht entfernt oder anderweitig optimiert. |
allowshrinking |
Ermöglicht das Entfernen der angegebenen Elemente, wenn R8 keine Verweise darauf findet. Die Elemente werden jedoch nicht umbenannt oder anderweitig optimiert. |
includedescriptorclasses |
Weist R8 an, alle Klassen beizubehalten, die in den Deskriptoren der beibehaltenen Methoden (Parametertypen und Rückgabetypen) und Felder (Feldtypen) vorkommen. |
allowaccessmodification |
Ermöglicht R8, die Zugriffsmodifikatoren (public, private, protected) von Klassen, Methoden und Feldern während des Optimierungsprozesses zu ändern (in der Regel zu erweitern). |
allowrepackage |
Ermöglicht es R8, Klassen in verschiedene Pakete zu verschieben, einschließlich des Standardpakets (Stammpaket). |
Klassenspezifikation
Sie müssen in jeder Keep-Regel eine Klasse angeben, einschließlich Schnittstellen-, Enum- und Annotationsklassen. Optional können Sie die Regel anhand von Annotationen einschränken, indem Sie eine Basisklasse oder eine implementierte Schnittstelle oder den Zugriffsmodifikator für die Klasse angeben. Alle Klassen, einschließlich Klassen aus dem Namespace java.lang wie java.lang.String, müssen mit ihrem voll qualifizierten Java-Namen angegeben werden. Wenn Sie wissen möchten, welche Namen verwendet werden sollten, prüfen Sie den Bytecode mit den Tools, die unter Generierte Java-Namen prüfen beschrieben sind.
Das folgende Beispiel zeigt, wie Sie die Klasse MaterialButton angeben sollten:
- Richtig:
com.google.android.material.button.MaterialButton - Falsch:
MaterialButton
Klassenspezifikationen geben auch die Elemente innerhalb einer Klasse an, die beibehalten werden sollen. Mit der folgenden Regel werden beispielsweise die Klasse MyClass und die Methode someSpecificMethod() beibehalten:
-keep class com.example.MyClass {
void someSpecificMethod();
}
Klassen anhand von Annotationen angeben
Wenn Sie Klassen anhand ihrer Annotationen angeben möchten, stellen Sie dem voll qualifizierten Java-Namen der Annotation das Symbol @ voran. Beispiel:
-keep class @com.example.MyAnnotation com.example.MyClass
Wenn eine Beibehaltungsregel mehr als eine Annotation hat, werden Klassen beibehalten, die alle aufgeführten Annotationen haben. Sie können mehrere Annotationen auflisten, aber die Regel gilt nur, wenn die Klasse alle aufgeführten Annotationen hat. Mit der folgenden Regel werden beispielsweise alle Klassen beibehalten, die sowohl mit Annotation1 als auch mit Annotation2 annotiert sind.
-keep class @com.example.Annotation1 @com.example.Annotation2 *
Unterklassen und Implementierungen angeben
Wenn Sie auf eine Unterklasse oder eine Klasse abzielen möchten, die eine Schnittstelle implementiert, verwenden Sie extend bzw. implements.
Beispiel: Sie haben die Klasse Bar mit der Unterklasse Foo:
class Foo : Bar()
Mit der folgenden Keep-Regel werden alle Unterklassen von Bar beibehalten. Beachten Sie, dass die Beibehaltungsregel die Basisklasse Bar selbst nicht enthält.
-keep class * extends Bar
Wenn Sie die Klasse Foo haben, die die Schnittstelle Bar implementiert:
class Foo : Bar
Mit der folgenden Keep-Regel werden alle Klassen beibehalten, die Bar implementieren. Beachten Sie, dass die Beibehaltungsregel die Schnittstelle Bar selbst nicht enthält.
-keep class * implements Bar
Klassen basierend auf Zugriffsmodifikatoren angeben
Sie können Zugriffsmodifikatoren wie public, private, static und final angeben, um Ihre Keep-Regeln präziser zu gestalten.
Mit der folgenden Regel werden beispielsweise alle public-Klassen im Paket api und seinen Unterpaketen sowie alle öffentlichen und geschützten Elemente in diesen Klassen beibehalten.
-keep public class com.example.api.** { public protected *; }
Sie können auch Modifikatoren für die Mitglieder innerhalb einer Klasse verwenden. Mit der folgenden Regel werden beispielsweise nur die public static-Methoden einer Utils-Klasse beibehalten:
-keep class com.example.Utils {
public static void *(...);
}
Kotlin-spezifische Modifikatoren
R8 unterstützt keine Kotlin-spezifischen Modifikatoren wie internal und suspend.
Beachten Sie die folgenden Richtlinien, um solche Felder beizubehalten.
Wenn Sie eine
internal-Klasse, -Methode oder ‑Feld beibehalten möchten, behandeln Sie sie als öffentlich. Betrachten Sie beispielsweise die folgende Kotlin-Quelle:package com.example internal class ImportantInternalClass { internal val f: Int internal fun m() {} }Die Klassen, Methoden und Felder von
internalsindpublicin den vom Kotlin-Compiler erstellten.class-Dateien. Daher müssen Sie das Keywordpublicverwenden, wie im folgenden Beispiel gezeigt:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }Wenn ein
suspend-Mitglied kompiliert wird, muss seine kompilierte Bytecode-Signatur in der Keep-Regel übereinstimmen.Betrachten Sie beispielsweise eine konkrete Repository-Klasse, die in Kotlin so definiert ist:
package com.example.repository import com.example.model.User class UserRepository { suspend fun fetchUser(id: String): User { // Implementation details... } }Wenn der Kotlin-Compiler diese Klasse in Bytecode kompiliert, werden
suspend-Funktionen einer CPS-Transformation (Continuation-Passing Style) unterzogen. Der Modifikatorsuspendwird entfernt, der Rückgabetyp wird injava.lang.Objectgeändert und der Methodensignatur wird einkotlin.coroutines.Continuation-Parameter hinzugefügt, um die asynchrone Zustandsmaschine zu verwalten.Die kompilierte Methodensignatur im Bytecode sieht so aus:
public final Object fetchUser(String id, Continuation<? super User> continuation);Wenn Sie eine Keep-Regel für diese Funktion schreiben möchten, können Sie die Kotlin-Syntax
suspendnicht verwenden. Stattdessen müssen Sie die kompilierte Bytecode-Signatur abgleichen (insbesondere mit Verweis aufkotlin.coroutines.Continuation) oder...verwenden.Ein Beispiel, das der genauen kompilierten Bytecode-Signatur entspricht, sieht so aus:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }Hier ein Beispiel mit
...:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Mitgliederspezifikation
Die Klassenspezifikation enthält optional die Klassenelemente, die beibehalten werden sollen. Wenn Sie ein oder mehrere Mitglieder für einen Kurs angeben, gilt die Regel nicht für andere Mitglieder.
Mitglieder anhand von Annotationen angeben
Sie können Mitglieder anhand ihrer Anmerkungen angeben. Ähnlich wie bei Klassen stellen Sie dem voll qualifizierten Java-Namen der Annotation @ voran. So können Sie nur die Mitglieder innerhalb einer Klasse beibehalten, die mit bestimmten Annotationen gekennzeichnet sind. Wenn Sie beispielsweise Methoden und Felder beibehalten möchten, die mit @com.example.MyAnnotation annotiert sind:
-keep class com.example.MyClass {
@com.example.MyAnnotation <methods>;
@com.example.MyAnnotation <fields>;
}
Sie können dies mit dem Abgleich von Annotationen auf Klassenebene kombinieren, um leistungsstarke, gezielte Regeln zu erstellen:
-keep class @com.example.ClassAnnotation * {
@com.example.MethodAnnotation <methods>;
@com.example.FieldAnnotation <fields>;
}
So werden Klassen, die mit @ClassAnnotation annotiert sind, und in diesen Klassen Methoden, die mit @MethodAnnotation annotiert sind, sowie Felder, die mit @FieldAnnotation annotiert sind, beibehalten.
Verwenden Sie nach Möglichkeit anmerkungsbasierte Beibehaltungsregeln. Dieser Ansatz bietet eine explizite Verknüpfung zwischen Ihrem Code und Ihren Beibehaltungsregeln und führt oft zu robusteren Konfigurationen. Die Annotationsbibliothek androidx.annotation verwendet beispielsweise diesen Mechanismus.
Für die Reflexion in Ihrem eigenen Code können Sie auch Jetpack Keep verwenden. Anstatt Beibehaltungsregeln zu schreiben, die auf Annotationen abzielen, annotieren Sie Ihren Code und ein Gradle-Plug-in generiert die Beibehaltungsregeln für Sie.
Methoden
Die Syntax zum Angeben einer Methode in der Mitgliedsspezifikation für eine Keep-Regel lautet so:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Mit der folgenden Beibehaltungsregel wird beispielsweise eine öffentliche Methode namens getUserId() beibehalten, die ein String zurückgibt.
-keep class com.example.model.UserData {
public java.lang.String getUserId();
}
Sie können <methods> als Tastenkombination verwenden, um alle Methoden in einer Klasse so abzugleichen:
-keep class com.example.model.UserData {
<methods>;
}
Weitere Informationen zum Angeben von Typen für Rückgabetypen und Parametertypen finden Sie unter Typen.
Konstruktoren
Verwenden Sie <init>, um einen Konstruktor anzugeben. Die Syntax zum Angeben eines Konstruktors in der Mitgliedsspezifikation für eine Keep-Regel lautet so:
[<access_modifier>] <init>(parameter_types);
Mit der folgenden Keep-Regel wird beispielsweise ein Konstruktor für einen UI-State-Holder beibehalten, der eine Repository-Instanz verwendet.
-keep class com.example.ui.state.UserViewModel {
public <init>(com.example.repository.UserDataRepository);
}
Wenn Sie alle öffentlichen Konstruktoren beibehalten möchten, verwenden Sie das folgende Beispiel als Referenz:
-keep class com.example.ui.state.UserViewModel {
public <init>(...);
}
Felder
Die Syntax zum Angeben eines Felds in der Mitgliederspezifikation für eine Beibehaltungsregel lautet so:
[<access_modifier>...] [<type>] <field_name>;
Mit der folgenden Beibehaltungsregel wird beispielsweise ein privates Stringfeld mit dem Namen userId und ein öffentliches statisches Ganzzahlfeld mit dem Namen STATUS_ACTIVE beibehalten:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Sie können <fields> als Kürzel verwenden, um alle Felder in einer Klasse so abzugleichen:
-keep class com.example.models.User {
<fields>;
}
Typen
In diesem Abschnitt wird beschrieben, wie Sie Rückgabetypen, Parametertypen und Feldtypen in Mitgliederspezifikationen für Beibehaltungsregeln angeben. Verwenden Sie die generierten Java-Namen, um Typen anzugeben, wenn sie sich vom Kotlin-Quellcode unterscheiden.
Einfache Typen
Verwenden Sie das entsprechende Java-Keyword, um einen primitiven Typ anzugeben. R8 erkennt die folgenden primitiven Typen: boolean, byte, short, char, int, long, float, double.
Hier ist ein Beispiel für eine Regel mit einem primitiven Typ:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Generische Typen
Während der Kompilierung löscht der Kotlin-/Java-Compiler Informationen zu generischen Typen. Wenn Sie also Keep-Regeln mit generischen Typen schreiben, müssen Sie die kompilierte Darstellung Ihres Codes als Ziel festlegen und nicht den ursprünglichen Quellcode. Weitere Informationen dazu, wie generische Typen geändert werden, finden Sie unter Type Erasure.
Angenommen, Sie haben den folgenden Code mit einem ungebundenen generischen Typ, der in Box.kt definiert ist:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Nach dem Löschen des Typs wird T durch Object ersetzt. Damit der Klassenkonstruktor und die Methode beibehalten werden, muss in Ihrer Regel java.lang.Object anstelle des generischen T verwendet werden.
Eine Beispielregel zum Beibehalten von Daten könnte so aussehen:
# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
public init(java.lang.Object);
public java.lang.Object getItem();
}
Wenn Sie den folgenden Code mit einem eingeschränkten generischen Typ in NumberBox.kt haben:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
In diesem Fall wird T durch seine Begrenzung java.lang.Number ersetzt.
Eine Beispielregel zum Beibehalten von Daten könnte so aussehen:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Wenn Sie anwendungsspezifische generische Typen als Basisklasse verwenden, müssen Sie auch Keep-Regeln für die Basisklassen einfügen.
Beispiel:
package com.myapp.data
data class UnpackOptions(val useHighPriority: Boolean)
// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}
Sie können eine Keep-Regel mit includedescriptorclasses verwenden, um sowohl die Klasse UnpackOptions als auch die Klassenmethode Box mit einer einzigen Regel beizubehalten:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Wenn Sie eine bestimmte Funktion beibehalten möchten, die eine Liste von Objekten verarbeitet, müssen Sie eine Regel schreiben, die genau der Signatur der Funktion entspricht. Da generische Typen gelöscht werden, wird ein Parameter wie List<Product> als java.util.List betrachtet.
Angenommen, Sie haben eine Dienstprogrammklasse mit einer Funktion, die eine Liste von Product-Objekten so verarbeitet:
package com.myapp.utils
import com.myapp.data.Product
import android.util.Log
class DataProcessor {
// This is the function we want to keep
fun processProducts(products: List<Product>) {
Log.d("DataProcessor", "Processing ${products.size} products.")
// Business logic ...
}
}
// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)
Mit der folgenden Beibehaltungsregel können Sie nur die Funktion processProducts schützen:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Array-Typen
Geben Sie einen Arraytyp an, indem Sie für jede Dimension des Arrays [] an den Komponententyp anhängen. Dies gilt sowohl für Klassentypen als auch für primitive Typen.
- Eindimensionales Klassenarray:
java.lang.String[] - Zweidimensionales primitives Array:
int[][]
Beispiel:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Sie können die folgende Aufbewahrungsregel verwenden:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Beispiele
Wenn Sie beispielsweise eine bestimmte Klasse und alle ihre Mitglieder beibehalten möchten, verwenden Sie Folgendes:
-keep class com.myapp.MyClass { *; }
Wenn Sie nur die Klasse mit ihrem Standardkonstruktor, aber keine anderen Elemente beibehalten möchten, verwenden Sie Folgendes:
-keep class com.myapp.MyClass
Es wird empfohlen, immer einige Mitglieder anzugeben. Im folgenden Beispiel werden das öffentliche Feld text und die öffentliche Methode updateText() in der Klasse MyClass beibehalten.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Wenn Sie alle öffentlichen Felder und öffentlichen Methoden beibehalten möchten, sehen Sie sich das folgende Beispiel an:
-keep public class com.example.api.ApiClient {
public *;
}
Mitgliederspezifikation weglassen
Wenn Sie die Mitgliedsspezifikation weglassen, behält R8 den Standardkonstruktor für die Klasse bei.
Wenn Sie beispielsweise -keep class com.example.MyClass oder -keep class com.example.MyClass {} schreiben, behandelt R8 diese so, als hätten Sie Folgendes geschrieben:
-keep class com.example.MyClass{
void <init>();
}
Muster für Mitgliedernamen negieren
Ab Android Gradle-Plug-in (AGP) 9.2.0 können Sie Mitgliedsnamenmuster in Ihren Beibehaltungsregeln negieren. So können Sie Mitglieder anhand von Mustern beibehalten oder ausschließen. Wenn Sie ein Muster negieren möchten, stellen Sie dem Muster für den Mitgliedsnamen ein Ausrufezeichen (!) voran.
Sie können diese Funktion verwenden, wenn Sie die meisten Mitglieder beibehalten möchten, die einem breiteren Muster entsprechen, aber bestimmte Mitglieder ausschließen möchten, z. B. Methoden, die nur für Tests vorgesehen sind.
Beispiel:
Wenn Sie alle öffentlichen Methoden in com.example.MyClass beibehalten möchten, mit Ausnahme der Methoden, die mit „ForTesting“ enden, verwenden Sie die folgende Regel:
-keepclassmembers class com.example.MyClass {
public *** !*ForTesting(...);
}
Funktionen auf Paketebene
Wenn Sie auf eine Kotlin-Funktion verweisen möchten, die außerhalb einer Klasse definiert ist (allgemein als Funktionen auf oberster Ebene bezeichnet), müssen Sie den generierten Java-Namen für die Klasse verwenden, die implizit vom Kotlin-Compiler hinzugefügt wurde. Der Klassenname ist der Kotlin-Dateiname mit dem Suffix Kt. Wenn Sie beispielsweise eine Kotlin-Datei namens MyClass.kt haben, die so definiert ist:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
Wenn Sie eine Keep-Regel für die Funktion isEmailValid schreiben möchten, muss die Klassenspezifikation auf die generierte Klasse MyClassKt ausgerichtet sein:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Platzhalter
In der folgenden Tabelle sehen Sie, wie Sie Platzhalter verwenden, um Keep-Regeln auf mehrere Klassen oder Elemente anzuwenden, die einem bestimmten Muster entsprechen.
| Wildcard | Gilt für Kurse oder Mitglieder | Beschreibung |
|---|---|---|
| ** | Beides | Am häufigsten verwendet. Entspricht einem beliebigen Typnamen, einschließlich einer beliebigen Anzahl von Pakettrennzeichen. Das ist nützlich, um alle Klassen in einem Paket und seinen Unterpaketen abzugleichen. |
| * | Beides | Bei Klassenspezifikationen wird jeder Teil eines Typnamens abgeglichen, der keine Paketseparatoren (.) enthält. Bei Memberspezifikationen wird jeder Methoden- oder Feldname abgeglichen. Wenn es allein verwendet wird, ist es auch ein Alias für **. |
| ? | Beides | Entspricht einem beliebigen einzelnen Zeichen in einem Klassen- oder Mitgliedsnamen. |
| *** | Mitglieder | Entspricht jedem Typ, einschließlich primitiver Typen (wie int), Klassentypen (wie java.lang.String) und Array-Typen beliebiger Dimension (wie byte[][]). |
| … | Mitglieder | Stimmt mit jeder Parameterliste für eine Methode überein. |
| % | Mitglieder | Entspricht einem beliebigen primitiven Typ, z. B. int, float oder boolean. |
Hier einige Beispiele für die Verwendung der speziellen Platzhalter:
Wenn Sie mehrere Methoden mit demselben Namen haben, die unterschiedliche primitive Typen als Eingaben verwenden, können Sie
%verwenden, um eine Keep-Regel zu schreiben, die alle beibehält. Die KlasseDataStorehat beispielsweise mehreresetValue-Methoden:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }Mit der folgenden Beibehaltungsregel werden alle Methoden beibehalten:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }Wenn Sie mehrere Klassen mit Namen haben, die sich nur durch ein Zeichen unterscheiden, können Sie mit
?eine Beibehaltungsregel schreiben, die alle Klassen beibehält. Beispiel:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}Mit der folgenden Keep-Regel werden alle Klassen beibehalten:
-keep class com.example.models.UserV?Wenn Sie die Klassen
ExampleundAnotherExample(wenn es sich um Klassen auf Stammebene handelt), aber nichtcom.foo.Exampleabgleichen möchten, verwenden Sie die folgende Beibehaltungsregel:-keep class *ExampleWenn Sie * allein verwenden, fungiert es als Alias für **. Die folgenden Aufbewahrungsregeln sind beispielsweise gleichwertig:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Regeln für die bedingte Aufbewahrung
Zusätzlich zu den Standardaufbewahrungsregeln können Sie bedingte Aufbewahrungsregeln verwenden, die nur angewendet werden, wenn eine bestimmte Bedingung erfüllt ist. Mit dem Flag -if können Sie bedingte Regeln angeben. Die Beibehaltungsregel, die auf ein -if-Flag folgt, ist nur aktiv, wenn die Klassenspezifikation im -if-Flag eine Übereinstimmung hat.
Bedingte Keep-Regeln sind besonders nützlich, wenn Sie es mit Bibliotheken oder Codemustern zu tun haben, die Reflection verwenden. In diesem Fall ist eine Keep-Regel nur erforderlich, wenn eine bestimmte Klasse oder ein bestimmtes Mitglied vorhanden ist oder einem Muster entspricht. Durch die Verwendung bedingter Regeln können Sie die Größe Ihrer App minimieren, da unnötiger Code nicht beibehalten wird.
Die allgemeine Syntax für eine bedingte Beibehaltungsregel lautet wie folgt:
-if <class_specification_if> <keep_rule>
Wenn die Klassenspezifikation in einer -if-Bedingung Platzhalter enthält (z. B. * oder **), wird die Zeichenfolge, die mit einem Platzhalter übereinstimmt, erfasst.
Sie können in der nachfolgenden Beibehaltungsregel mit Rückverweisen auf diese erfassten Strings verweisen: <1> bezieht sich auf den String, der vom ersten Platzhalter erfasst wurde, <2> auf den String, der vom zweiten Platzhalter erfasst wurde, usw.
Die Jetpack Navigation-Komponente generiert beispielsweise NavArgs-Klassen für die typsichere Übergabe von Argumenten zwischen Zielen. Wenn Sie den NavArgsLazy-Delegate verwenden, wird die Reflektion verwendet, um eine statische fromBundle-Methode in der generierten NavArgs-Klasse zu finden und aufzurufen, um Argumente zu deserialisieren. Wenn Ihre App NavArgs verwendet, müssen Sie die Methode fromBundle nur für Klassen beibehalten, die die Schnittstelle NavArgs implementieren.
Mit einer bedingten Keep-Regel können Sie festlegen, dass R8 die Methode fromBundle für eine bestimmte Klasse beibehalten soll, wenn eine Klasse androidx.navigation.NavArgs implementiert:
# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
public static ** fromBundle(android.os.Bundle);
}
In diesem Beispiel ist ** der erste und einzige Platzhalter in der Bedingung -if. Er entspricht dem Klassennamen jeder Klasse, die androidx.navigation.NavArgs implementiert. Der String, der mit ** übereinstimmt (in diesem Fall der Klassenname), wird erfasst und Sie können in der nachfolgenden Regel mit <1> darauf verweisen. Die Regel -keepclassmembers gilt also für jede Klasse, die androidx.navigation.NavArgs implementiert und mit der Bedingung -if übereinstimmt. Wenn R8 keine Klassen findet, die NavArgs implementieren, wird diese Keep-Regel ignoriert.
Ein weiterer häufiger Anwendungsfall sind JSON-Serialisierungsbibliotheken wie Gson. Wenn Ihre Datenmodellklassen die Gson-Annotation @SerializedName für ein Feld verwenden, können Sie eine bedingte Regel verwenden, um solche Klassen und ihre Mitglieder zu schützen, die Gson für die Reflexion benötigt:
# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
@com.google.gson.annotations.SerializedName <fields>;
<init>(...);
}
Rückverweise erfassen Strings, die Teilstrings von Klassennamen sein können, wenn ein Platzhalter nur mit einem Teil des Namens übereinstimmt. Wenn Sie beispielsweise -if class com.example.*X* verwenden, erfasst R8 den Teilstring vor X als <1> und den Teilstring nach X als <2>. In der folgenden Regel wird dies verwendet, um alle Klassennamen zu finden, die X enthalten, und eine entsprechende Klasse beizubehalten, in der X durch Y ersetzt wird:
# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>
Bedingte Beibehaltungsregeln für die Reflexion
Ein häufiger Anwendungsfall für bedingte Keep-Regeln ist die Verarbeitung von Reflection, bei der bestimmte Methoden oder Klassen zur Laufzeit dynamisch aufgerufen werden. Wenn eine Bibliothek beispielsweise Reflexion verwendet, um mit Ihrem Code zu interagieren, müssen Sie möglicherweise nur bestimmte Mitglieder beibehalten, wenn Sie eine bestimmte Funktion dieser Bibliothek verwenden.
Die Jetpack Navigation-Bibliothek verwendet Reflexion mit dem NavArgsLazy-Delegat, um eine statische fromBundle-Methode für generierte NavArgs-Klassen für die typsichere Argumentübergabe aufzurufen. Damit diese Methode nur für NavArgs-Implementierungen und nicht für jede Klasse beibehalten wird, enthält Jetpack Navigation die folgende bedingte Keep-Regel:
# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
public static ** fromBundle(android.os.Bundle);
}
Mit dieser Regel wird fromBundle nur für die Klassen beibehalten, die es benötigen, anstatt es in allen Klassen beizubehalten oder Sie manuell angeben zu lassen, welche Klassen es benötigen.
Generierte Java-Namen prüfen
Wenn Sie Keep-Regeln schreiben, müssen Sie Klassen und andere Referenztypen mit ihren Namen angeben, nachdem sie in Java-Bytecode kompiliert wurden. Beispiele finden Sie unter Klassenspezifikation und Typen. Wenn Sie wissen möchten, wie die generierten Java-Namen für Ihren Code lauten, verwenden Sie eines der folgenden Tools in Android Studio:
- APK Analyzer
- Öffnen Sie die Kotlin-Quelldatei und prüfen Sie den Bytecode über Tools > Kotlin > Show Kotlin Bytecode > Decompile.