Eine Keep-Regel gibt auf hoher Ebene eine Klasse (oder Unterklasse oder Implementierung) und dann die beizubehaltenden Elemente (Methoden, Konstruktoren oder Felder) innerhalb dieser Klasse an.
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 Beibehalten-Regel, in der keepclassmembers als Beibehalten-Option, allowoptimization als Modifikator verwendet wird und someSpecificMethod() aus com.example.MyClass beibehalten wird:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Option „Beibehalten“
Die Option „Beibehalten“ ist der erste Teil Ihrer Aufbewahrungsregel. Damit wird angegeben, welche Aspekte einer Klasse beibehalten werden sollen. Es gibt sechs verschiedene Optionen zum Beibehalten von Daten: keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames und keepclasseswithmembernames.
In der folgenden Tabelle werden diese Optionen beschrieben:
| Option „Behalten“ | Beschreibung |
|---|---|
keepclassmembers |
Behält angegebene Elemente nur bei, wenn R8 die Klasse, die sie enthält, nicht entfernt. |
keep |
Behält die angegebenen Klassen und die angegebenen Elemente (Felder und Methoden) bei und verhindert, dass sie optimiert werden. Hinweis: keep sollte in der Regel nur mit Optionenmodifizierern vom Typ „Beibehalten“ verwendet werden, da keep allein verhindert, dass Optimierungen 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 hat. |
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 das vollständige Entfernen, 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. Sie verhindert nicht das Entfernen von Code. 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 Keep-Option ist entscheidend für die richtige Optimierung Ihrer App. Bei bestimmten Keep-Optionen wird Code verkleinert, indem nicht referenzierter Code entfernt wird. Bei anderen wird Code verschleiert oder umbenannt. In der folgenden Tabelle sind die Aktionen der verschiedenen Beibehalteoptionen aufgeführt:
| Option „Behalten“ | Klassen verkleinern | Klassen verschleiern | Mitglieder schrumpfen | Mitglieder werden verschleiert |
|---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modifikator „Option beibehalten“
Mit einem Modifikator für die Option „Beibehalten“ wird der Bereich und das Verhalten einer Keep-Regel gesteuert. Sie können Ihrer Aufbewahrungsregel null oder mehr Modifikatoren für die Aufbewahrungsoption 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 Zugriffsmodifizierer (public, private, protected) von Klassen, Methoden und Feldern während der Optimierung 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. Um die Namen zu ermitteln, die verwendet werden sollten, sehen Sie sich den Bytecode mit den unter Generierte Java-Namen prüfen beschriebenen Tools an.
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 basierend auf Annotationen angeben
Wenn Sie Klassen anhand ihrer Annotationen angeben möchten, stellen Sie dem vollständig qualifizierten Java-Namen der Annotation das Symbol @ voran. Beispiel:
-keep class @com.example.MyAnnotation com.example.MyClass
Wenn eine Keep-Regel mehrere Anmerkungen enthält, werden Klassen beibehalten, die alle aufgeführten Anmerkungen haben. Sie können mehrere Anmerkungen auflisten, die Regel wird jedoch nur angewendet, wenn die Klasse alle aufgeführten Anmerkungen enthält. 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, die eine Schnittstelle implementiert, ausrichten möchten, 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. Die Keep-Regel schließt die Superklasse Bar selbst nicht ein.
-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. Die Aufbewahrungsregel umfasst nicht die Schnittstelle Bar selbst.
-keep class * implements Bar
Klassen basierend auf Zugriffsmodifizierern angeben
Sie können Zugriffsmodifizierer wie public, private, static und final angeben, um Ihre Aufbewahrungsregeln 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 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 Modifizierer 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 den folgenden Kotlin-Quellcode: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-Element kompiliert wird, muss die kompilierte Bytecode-Signatur in der Keep-Regel übereinstimmen.Betrachten Sie beispielsweise eine konkrete Repository-Klasse aus der Praxis, 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 in den Continuation-Passing Style (CPS) transformiert. Der Modifikatorsuspendwird entfernt, der Rückgabetyp wird injava.lang.Objectgeändert und der Methodensignatur wird einkotlin.coroutines.Continuation-Parameter angehängt, um den asynchronen Zustandsautomaten 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 Signatur des kompilierten Bytecodes 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 beizubehaltenden Klassenmember. Wenn Sie ein oder mehrere Mitglieder für einen Kurs angeben, gilt die Regel nicht für andere Mitglieder.
Mitglieder anhand von Anmerkungen 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 Elemente einer Klasse beibehalten, die mit bestimmten Anmerkungen versehen 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 Anmerkungen auf Klassenebene kombinieren, um leistungsstarke, zielgerichtete 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 Aufbewahrungsregeln. Dieser Ansatz bietet eine explizite Verknüpfung zwischen Ihrem Code und Ihren Keep-Regeln und führt häufig zu robusteren Konfigurationen. Die Annotationsbibliothek androidx.annotation verwendet beispielsweise diesen Mechanismus.
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 Keep-Regel wird beispielsweise eine öffentliche Methode namens getUserId() beibehalten, die einen String zurückgibt.
-keep class com.example.model.UserData {
public java.lang.String getUserId();
}
Sie können <methods> als Abkürzung verwenden, um alle Methoden in einer Klasse 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-Status-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 Keep-Regel ist so:
[<access_modifier>...] [<type>] <field_name>;
Die folgende Keep-Regel behält beispielsweise ein privates String-Feld mit dem Namen userId und ein öffentliches statisches Ganzzahlfeld mit dem Namen STATUS_ACTIVE bei:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Sie können <fields> als Abkürzung verwenden, um alle Felder in einer Klasse abzugleichen:
-keep class com.example.models.User {
<fields>;
}
Typen
In diesem Abschnitt wird beschrieben, wie Sie Rückgabetypen, Parametertypen und Feldtypen in den Mitgliedsspezifikationen von Keep-Regeln angeben. Denken Sie daran, die generierten Java-Namen zu verwenden, um Typen anzugeben, wenn sie sich vom Kotlin-Quellcode unterscheiden.
Einfache Typen
Verwenden Sie das Java-Schlüsselwort des jeweiligen primitiven Typs, um ihn 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 generische Typinformationen. Wenn Sie also Keep-Regeln schreiben, die generische Typen enthalten, müssen Sie auf die kompilierte Darstellung Ihres Codes und nicht auf den ursprünglichen Quellcode abzielen. 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 Grenze 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 app-spezifische 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 Utility-Klasse 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 Keep-Regel wird nur die processProducts-Funktion geschützt:
-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. Das gilt sowohl für Klassen- 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 Elemente 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 *;
}
Mitgliedsspezifikation weglassen
Wenn Sie die Mitgliederspezifikation 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>();
}
Mustern für Mitgliedernamen negieren
Ab Android Gradle-Plug-in (AGP) 9.2.0 können Sie Mustern für Mitgliedernamen in Ihren Keep-Regeln negieren. So können Sie Mitglieder anhand von Mustern beibehalten oder ausschließen. Um ein Muster zu negieren, stellen Sie dem Mitgliedernamenmuster ein Ausrufezeichen (!) voran.
Sie können diese Funktion in Situationen verwenden, in denen 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 der obersten Ebene bezeichnet), müssen Sie den generierten Java-Namen für die Klasse verwenden, die vom Kotlin-Compiler implizit hinzugefügt wird. 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 Paketseparatoren. 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 Arraytypen beliebiger Dimension (wie byte[][]). |
| … | Mitglieder | Entspricht einer beliebigen Liste von Parametern für eine Methode. |
| % | Mitglieder | Entspricht einem beliebigen primitiven Typ, z. B. int, float oder boolean. |
Hier sind 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 mit
%eine Keep-Regel schreiben, mit der alle beibehalten werden. 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 Keep-Regel werden alle Methoden beibehalten:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }Wenn Sie mehrere Klassen mit Namen haben, die sich um ein Zeichen unterscheiden, verwenden Sie
?, um eine Keep-Regel zu schreiben, mit der alle beibehalten werden. 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(sofern sie Klassen auf Stammebene sind), aber nichtcom.foo.Exampleabgleichen möchten, verwenden Sie die folgende Keep-Regel:-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 Aufbewahrungsregel, die auf ein -if-Flag folgt, ist nur aktiv, wenn die Klassenspezifikation im -if-Flag eine Übereinstimmung aufweist.
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. Mit bedingten Regeln können Sie die Größe Ihrer App minimieren, da unnötiger Code nicht beibehalten wird.
Die allgemeine Syntax für eine bedingte Aufbewahrungsregel lautet wie folgt:
-if <class_specification_if> <keep_rule>
Wenn die Klassenspezifikation in einer -if-Bedingung Platzhalter (z. B. * oder **) enthält, wird die Zeichenfolge, die einem Platzhalter entspricht, erfasst.
Sie können in der nachfolgenden Aufbewahrungsregel 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 NavArgs-Schnittstelle 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 einer beliebigen Klasse, die androidx.navigation.NavArgs implementiert. Die Zeichenfolge, die mit ** übereinstimmt (in diesem Fall der Klassenname), wird erfasst und Sie können mit <1> in der nachfolgenden Regel darauf verweisen. Die -keepclassmembers-Regel gilt also für jede Klasse, die androidx.navigation.NavArgs implementiert und der -if-Bedingung entspricht. 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 Annotation @SerializedName von Gson für ein beliebiges Feld verwenden, können Sie eine bedingte Regel verwenden, um eine solche Klasse und ihre Elemente zu schützen, die Gson für die Reflektion 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>(...);
}
Mit Rückverweisen werden Strings erfasst, die Unterstrings von Klassennamen sein können, wenn ein Platzhalter nur einen Teil des Namens abgleicht. Wenn Sie beispielsweise -if class com.example.*X* verwenden, erfasst R8 den Teilstring vor X als <1> und den Teilstring nach X als <2>. Mit der folgenden Regel wird nach Klassennamen gesucht, die X enthalten. Eine entsprechende Klasse wird beibehalten, 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 Aufbewahrungsregeln für Spiegelung
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 Reflection verwendet, um mit Ihrem Code zu interagieren, müssen Sie möglicherweise nur bestimmte Elemente beibehalten, wenn Sie eine bestimmte Funktion dieser Bibliothek verwenden.
Die Jetpack Navigation-Bibliothek verwendet die Reflektion mit dem NavArgsLazy-Delegaten, um eine statische fromBundle-Methode für generierte NavArgs-Klassen für die typsichere Übergabe von Argumenten 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. Sie müssen also nicht manuell angeben, für welche Klassen es beibehalten werden soll.
Generierte Java-Namen prüfen
Wenn Sie Keep-Regeln schreiben, müssen Sie Klassen und andere Referenztypen anhand ihrer Namen angeben, nachdem sie in Java-Bytecode kompiliert wurden (siehe Beispiele unter Klassenspezifikation und Typen). So finden Sie heraus, welche Java-Namen für Ihren Code generiert wurden: 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.