Allgemein gibt eine Keep-Regel 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. Bei bestimmten Aufbewahrungsoptionen wird keep_option_modfier
jedoch nicht akzeptiert.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Im Folgenden finden Sie ein Beispiel für eine Keep-Regel, in der keepclassmembers
als Keep-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 Beibehaltungsregel. Damit wird angegeben, welche Aspekte einer Klasse beibehalten werden sollen. Es gibt sechs verschiedene Optionen zum Beibehalten: keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
, keepclasseswithmembernames
.
In der folgenden Tabelle werden diese Optionen beschrieben:
Option „Beibehalten“ | Beschreibung |
---|---|
keepclassmembers |
Behält nur angegebene Elemente bei, wenn die Klasse nach der Optimierung vorhanden ist. |
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 Modifizierern für die Option „Beibehalten“ verwendet werden, da keep allein verhindert, dass übereinstimmende Klassen optimiert werden. |
keepclasseswithmembers |
Eine Klasse und ihre angegebenen Mitglieder werden nur beibehalten, wenn die Klasse alle Mitglieder aus der Klassenspezifikation enthält. |
keepclassmembernames |
Verhindert das Umbenennen bestimmter Klassenmitglieder, aber nicht das Entfernen der Klasse oder ihrer Mitglieder. Hinweis:Die Bedeutung dieser Option wird häufig 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 häufig 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 Beibehaltungsoptionen aufgeführt:
Option „Beibehalten“ | Klassen verkleinern | Klassen verschleiern | Mitglieder reduzieren | Mitglieder verschleiern |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modifikator „Beibehalten“
Mit einem Modifikator für die Option „Beibehalten“ wird der Umfang und das Verhalten einer Keep-Regel gesteuert. Sie können Ihrer Aufbewahrungsregel beliebig viele 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. |
allowobfucastion |
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 (Stammpakets). |
Klassenspezifikation
Sie müssen eine Klasse, eine Superklasse oder eine implementierte Schnittstelle als Teil einer Keep-Regel angeben. Alle Klassen, einschließlich Klassen aus dem Namespace java.lang
wie java.lang.String
, müssen mit ihrem vollständig qualifizierten Java-Namen angegeben werden. Um die Namen zu ermitteln, die verwendet werden sollten, untersuchen Sie den Bytecode mit den in Generierte Java-Namen abrufen beschriebenen Tools.
Das folgende Beispiel zeigt, wie Sie die Klasse MaterialButton
angeben sollten:
- Richtig:
com.google.android.material.button.MaterialButton
- Falsch:
MaterialButton
In Klassenspezifikationen wird auch angegeben, welche Elemente in einer Klasse beibehalten werden sollen. Mit der folgenden Regel wird die Klasse MaterialButton
und alle ihre Elemente beibehalten:
-keep class com.google.android.material.button.MaterialButton { *; }
Unterklassen und Implementierungen
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 abgeleiteten Klasse Foo
:
class Foo : Bar()
Mit der folgenden Keep-Regel werden alle Unterklassen von Bar
beibehalten. Beachten Sie, dass die Keep-Regel nicht die Superklasse Bar
selbst enthält.
-keep class * extends Bar
Wenn Sie die Klasse Foo
haben, die Bar
implementiert:
class Foo : Bar
Mit der folgenden Keep-Regel werden alle Klassen beibehalten, die Bar
implementieren. Beachten Sie, dass die Keep-Regel die Schnittstelle Bar
selbst nicht enthält.
-keep class * implements Bar
Zugriffsmodifikator
Sie können Zugriffsmodifizierer wie public
, private
, static
und final
angeben, um Ihre Aufbewahrungsregeln präziser zu gestalten.
Die folgende Regel behält beispielsweise alle public
-Klassen im Paket api
und seinen Unterpaketen sowie alle öffentlichen und geschützten Elemente in diesen Klassen bei.
-keep public class com.example.api.** { public protected *; }
Sie können auch Modifikatoren für die Elemente innerhalb einer Klasse verwenden. Die folgende Regel behält beispielsweise nur die public static
-Methoden einer Utils
-Klasse bei:
-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 den folgenden Kotlin-Quellcode:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
Die Klassen, Methoden und Felder von
internal
sindpublic
in den vom Kotlin-Compiler erstellten.class
-Dateien. Daher müssen Sie das Keywordpublic
verwenden, wie im folgenden Beispiel gezeigt:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
Wenn ein
suspend
-Element kompiliert wird, muss seine kompilierte Signatur mit der Keep-Regel übereinstimmen.Wenn Sie beispielsweise die Funktion
fetchUser
wie im folgenden Snippet definiert haben:suspend fun fetchUser(id: String): User
Nach der Kompilierung sieht die Signatur im Bytecode so aus:
public final Object fetchUser(String id, Continuation<? super User> continuation);
Wenn Sie eine Keep-Regel für diese Funktion schreiben möchten, müssen Sie diese kompilierte Signatur abgleichen oder
...
verwenden.Hier ein Beispiel für die Verwendung der kompilierten Signatur:
-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 Klassenmitglieder. Wenn Sie ein oder mehrere Mitglieder für eine Klasse angeben, gilt die Regel nur für diese Mitglieder.
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 und nicht ihre Elemente beibehalten möchten, verwenden Sie Folgendes:
-keep class com.myapp.MyClass
In den meisten Fällen möchten Sie bestimmte Mitglieder angeben. 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 *;
}
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 mit dem Namen setLabel()
beibehalten, die „void“ zurückgibt und ein String
akzeptiert.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Sie können <methods>
als Abkürzung verwenden, um alle Methoden in einer Klasse abzugleichen:
-keep class com.example.MyView {
<methods>;
}
Weitere Informationen zum Angeben von Typen für Rückgabe- 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 benutzerdefinierter View
-Konstruktor beibehalten, der ein Context
und ein AttributeSet
akzeptiert.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Wenn Sie alle öffentlichen Konstruktoren beibehalten möchten, verwenden Sie das folgende Beispiel als Referenz:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Felder
Die Syntax zum Angeben eines Felds in der Mitgliedsspezifikation für eine Beibehalteregel lautet so:
[<access_modifier>...] [<type>] <field_name>;
Mit der folgenden Keep-Regel wird beispielsweise ein privates String-Feld namens userId
und ein öffentliches statisches Integer-Feld namens STATUS_ACTIVE
beibehalten:
-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>;
}
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
. Angenommen, Sie haben eine Kotlin-Datei mit dem Namen MyClass.kt
, 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);
}
Typen
In diesem Abschnitt wird beschrieben, wie Sie Rückgabetypen, Parametertypen und Feldtypen in Mitgliedsspezifikationen für Keep-Regeln angeben. Verwenden Sie die generierten Java-Namen, um Typen anzugeben, wenn sie sich vom Kotlin-Quellcode unterscheiden.
Einfache Typen
Um einen primitiven Typ anzugeben, verwenden Sie das entsprechende Java-Schlüsselwort. R8 erkennt die folgenden primitiven Typen: boolean
, byte
, short
, char
, int
, long
, float
, double
.
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 sieht so aus:
# 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 die Typauslöschung durch die zugehörige Grenze java.lang.Number
ersetzt.
Eine Beispielregel zum Beibehalten von Daten sieht so aus:
-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 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 Aufbewahrungsregel 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 []
an den Komponententyp für jede Dimension des Arrays anhängen. Das gilt sowohl für Klassen- als auch für primitive Typen.
- Eindimensionales Klassenarray:
java.lang.String[]
- Zweidimensionales primitives Array:
int[][]
Angenommen, Sie haben den folgenden Code:
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();
}
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.
Platzhalter | Gilt für Kurse oder Mitglieder | Beschreibung |
---|---|---|
** | Beides | Am häufigsten verwendet. Entspricht einem beliebigen Typnamen, einschließlich einer beliebigen Anzahl von Paket-Trennzeichen. 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 | Entspricht einer beliebigen Liste von Parametern für eine Methode. |
% | Mitglieder | Entspricht jedem primitiven Typ (z. B. „int“, „float“, „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 mit
%
eine Keep-Regel schreiben, mit der alle beibehalten werden. Die KlasseDataStore
hat 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. Angenommen, Sie haben die folgenden Klassen: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
Example
undAnotherExample
(falls sie Klassen auf Stammebene wären), aber nichtcom.foo.Example
beibehalten möchten, verwenden Sie die folgende Keep-Regel:-keep class *Example
Wenn 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[];) }
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 Klassenspezifikation und Typen für Beispiele). Mit den folgenden Tools in Android Studio können Sie prüfen, welche Java-Namen für Ihren Code generiert wurden:
- APK Analyzer
- Sehen Sie sich den Bytecode an, indem Sie die Kotlin-Quelldatei öffnen und Tools > Kotlin > Show Kotlin Bytecode > Decompile (Tools > Kotlin > Kotlin-Bytecode anzeigen > Dekompilieren) auswählen.