Ogólnie rzecz biorąc, reguła zachowywania określa klasę (lub podklasę lub implementację), a następnie elementy – metody, konstruktory lub pola – w tej klasie, które mają zostać zachowane.
Ogólna składnia reguły przechowywania jest następująca, jednak niektóre opcje przechowywania nie akceptują opcjonalnego znaku keep_option_modfier
.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Poniżej znajdziesz przykład reguły przechowywania, w której jako opcji przechowywania użyto keepclassmembers
, jako modyfikatora – allowoptimization
, a jako okresu przechowywania – someSpecificMethod()
od com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Opcja Zachowaj
Opcja zachowywania to pierwsza część reguły zachowywania. Określa, które aspekty klasy mają być zachowane. Dostępnych jest 6 opcji przechowywania: keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
i keepclasseswithmembernames
.
W tabeli poniżej znajdziesz opis tych opcji przechowywania:
Opcja Keep | Opis |
---|---|
keepclassmembers |
Zachowuje tylko określonych członków jeśli klasa istnieje po optymalizacji. |
keep |
Zachowuje określone klasy i określonych członków (pola i metody), uniemożliwiając ich optymalizację. Uwaga: keep należy zwykle stosować tylko z modyfikatorami opcji zachowania, ponieważ samo keep uniemożliwia optymalizację dopasowanych klas. |
keepclasseswithmembers |
Zachowuje klasę i jej określonych członków tylko wtedy, gdy klasa ma wszystkich członków z określenia klasy. |
keepclassmembernames |
Uniemożliwia zmianę nazwy określonych członków zajęć, ale nie uniemożliwia usunięcia zajęć ani ich członków. Uwaga: znaczenie tej opcji jest często błędnie interpretowane. Zamiast niej używaj równoważnej opcji -keepclassmembers,allowshrinking . |
keepnames |
Zapobiega zmianie nazwy zajęć i ich uczestników, ale nie uniemożliwia ich całkowitego usunięcia, jeśli zostaną uznane za nieużywane. Uwaga: znaczenie tej opcji jest często błędnie interpretowane. Zamiast niej używaj równoważnej opcji -keep,allowshrinking . |
keepclasseswithmembernames |
Zapobiega zmianie nazw klas i ich określonych elementów, ale tylko wtedy, gdy elementy te występują w kodzie końcowym. Nie zapobiega to usuwaniu kodu. Uwaga: znaczenie tej opcji jest często błędnie rozumiane. Zamiast niej używaj równoważnej opcji -keepclasseswithmembers,allowshrinking . |
Wybieranie odpowiedniej opcji przechowywania
Wybór odpowiedniej opcji zachowania jest kluczowy dla określenia właściwej optymalizacji aplikacji. Niektóre opcje zachowania zmniejszają kod (proces, w którym usuwany jest nieużywany kod), a inne zaciemniają lub zmieniają nazwy kodu. W tabeli poniżej znajdziesz opis działania poszczególnych opcji przechowywania:
Opcja Keep | Zmniejszanie klas | zaciemnia klasy, | Zmniejsza liczbę członków | Zaciemnianie informacji o członkach |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modyfikator opcji Zachowaj
Modyfikator opcji przechowywania służy do kontrolowania zakresu i działania reguły przechowywania. Do reguły przechowywania możesz dodać 0 lub więcej modyfikatorów opcji przechowywania.
Możliwe wartości modyfikatora opcji zachowania są opisane w tej tabeli:
Wartość | Opis |
---|---|
allowoptimization |
Umożliwia optymalizację określonych elementów. Określone elementy nie zostaną jednak zmienione ani usunięte. |
allowobfucastion |
Umożliwia zmianę nazw określonych elementów. Elementy nie zostaną jednak usunięte ani w inny sposób zoptymalizowane. |
allowshrinking |
Umożliwia usunięcie określonych elementów, jeśli R8 nie znajdzie do nich odwołań. Elementy nie są jednak zmieniane ani optymalizowane w inny sposób. |
includedescriptorclasses |
Nakazuje R8 zachowanie wszystkich klas, które pojawiają się w deskryptorach zachowywanych metod (typy parametrów i typy zwracane) i pól (typy pól). |
allowaccessmodification |
Umożliwia R8 zmianę (zwykle rozszerzenie) modyfikatorów dostępu (public , private , protected ) klas, metod i pól podczas procesu optymalizacji. |
allowrepackage |
Umożliwia przenoszenie klas do różnych pakietów, w tym do pakietu domyślnego (głównego). |
Specyfikacja klasy
W ramach reguły keep musisz określić klasę, superklasę lub zaimplementowany interfejs. Wszystkie klasy, w tym klasy z przestrzeni nazw java.lang
, takie jak java.lang.String
, muszą być określone przy użyciu pełnej nazwy Java. Aby dowiedzieć się, jakich nazw należy używać, sprawdź kod bajtowy za pomocą narzędzi opisanych w sekcji Uzyskiwanie wygenerowanych nazw w Javie.
W tym przykładzie pokazujemy, jak określić klasę MaterialButton
:
- Prawidłowo:
com.google.android.material.button.MaterialButton
- Nieprawidłowo:
MaterialButton
Specyfikacje klas określają też członków w klasie, którzy powinni zostać zachowani. Ta reguła zachowuje klasę MaterialButton
i wszystkich jej członków:
-keep class com.google.android.material.button.MaterialButton { *; }
Podklasy i implementacje
Aby kierować reklamy na podklasę lub klasę, która implementuje interfejs, użyj odpowiednio symboli extend
i implements
.
Załóżmy, że masz klasę Bar
z podklasą Foo
:
class Foo : Bar()
Poniższa reguła przechowywania zachowuje wszystkie podklasy klasy Bar
. Pamiętaj, że reguła zachowywania nie obejmuje samej superklasy Bar
.
-keep class * extends Bar
Jeśli masz klasę Foo
, która implementuje Bar
:
class Foo : Bar
Poniższa reguła przechowywania zachowuje wszystkie klasy, które implementują Bar
. Pamiętaj, że reguła przechowywania nie obejmuje samego interfejsu Bar
.
-keep class * implements Bar
Modyfikator dostępu
Możesz określić modyfikatory dostępu, takie jak public
, private
, static
i final
, aby reguły przechowywania były bardziej precyzyjne.
Na przykład ta reguła zachowuje wszystkie klasy public
w pakiecie api
i jego podpakietach oraz wszystkich publicznych i chronionych członków tych klas.
-keep public class com.example.api.** { public protected *; }
Możesz też używać modyfikatorów dla członków w klasie. Na przykład poniższa reguła zachowuje tylko metody public static
klasy Utils
:
-keep class com.example.Utils {
public static void *(...);
}
Modyfikatory specyficzne dla języka Kotlin
R8 nie obsługuje modyfikatorów specyficznych dla języka Kotlin, takich jak internal
i suspend
.
Aby zachować takie pola, postępuj zgodnie z tymi wskazówkami.
Aby zachować klasę, metodę lub pole oznaczone symbolem
internal
, traktuj je jako publiczne. Na przykład rozważmy ten kod źródłowy w języku Kotlin:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
Klasy, metody i pola
internal
sąpublic
w plikach.class
wygenerowanych przez kompilator Kotlina, więc musisz użyć słowa kluczowegopublic
, jak pokazano w tym przykładzie:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
Gdy
suspend
element jest kompilowany, dopasuj jego skompilowany podpis w regule keep.Jeśli na przykład masz funkcję
fetchUser
zdefiniowaną w sposób pokazany w tym fragmencie:suspend fun fetchUser(id: String): User
Po skompilowaniu jego podpis w kodzie bajtowym wygląda tak:
public final Object fetchUser(String id, Continuation<? super User> continuation);
Aby utworzyć regułę przechowywania dla tej funkcji, musisz dopasować ten skompilowany podpis lub użyć
...
.Przykładowe użycie skompilowanego podpisu:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
Oto przykład użycia właściwości
...
:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Specyfikacja członka
Specyfikacja klasy może opcjonalnie zawierać członków klasy, którzy mają zostać zachowani. Jeśli określisz co najmniej 1 użytkownika zajęć, reguła będzie obowiązywać tylko w przypadku tych użytkowników.
Aby na przykład zachować konkretną klasę i wszystkich jej członków, użyj tego polecenia:
-keep class com.myapp.MyClass { *; }
Aby zachować tylko klasę, a nie jej członków, użyj tego polecenia:
-keep class com.myapp.MyClass
Zazwyczaj warto określić niektórych członków. Na przykład w tym przykładzie zachowujemy pole publiczne text
i metodę publiczną updateText()
w klasie MyClass
.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Aby zachować wszystkie pola publiczne i metody publiczne, zapoznaj się z tym przykładem:
-keep public class com.example.api.ApiClient {
public *;
}
Metody
Składnia określania metody w specyfikacji elementu reguły zachowywania jest taka:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Na przykład poniższa reguła zachowuje publiczną metodę o nazwie setLabel()
, która zwraca wartość void i przyjmuje argument String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Możesz użyć symbolu <methods>
jako skrótu, aby dopasować wszystkie metody w klasie w następujący sposób:
-keep class com.example.MyView {
<methods>;
}
Więcej informacji o określaniu typów zwracanych i typów parametrów znajdziesz w sekcji Typy.
Zespoły
Aby określić konstruktor, użyj <init>
. Składnia określania konstruktora w specyfikacji elementu reguły zachowywania jest taka:
[<access_modifier>] <init>(parameter_types);
Na przykład ta reguła zachowywania zachowuje niestandardowy konstruktor View
, który przyjmuje argumenty Context
i AttributeSet
.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Aby zachować wszystkie publiczne konstruktory, użyj tego przykładu jako odniesienia:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Fieldsem
Składnia określania pola w specyfikacji elementu reguły zachowywania jest następująca:
[<access_modifier>...] [<type>] <field_name>;
Na przykład ta reguła zachowywania zachowuje prywatne pole tekstowe o nazwie userId
i publiczne statyczne pole liczbowe o nazwie STATUS_ACTIVE
:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Możesz użyć symbolu <fields>
jako skrótu, aby dopasować wszystkie pola w klasie w ten sposób:
-keep class com.example.models.User {
<fields>;
}
Funkcje na poziomie pakietu
Aby odwołać się do funkcji Kotlina zdefiniowanej poza klasą (zwykle nazywanej funkcją najwyższego poziomu), użyj wygenerowanej nazwy Java dla klasy niejawnie dodanej przez kompilator Kotlina. Nazwa klasy to nazwa pliku Kotlin z dodatkiem Kt
. Jeśli na przykład masz plik Kotlin o nazwie
MyClass.kt
zdefiniowany w ten sposób:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
Aby napisać regułę zachowywania dla funkcji isEmailValid
, specyfikacja klasy musi być kierowana na wygenerowaną klasę MyClassKt
:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Typy
W tej sekcji opisujemy, jak określać typy zwracanych wartości, typy parametrów i typy pól w specyfikacjach elementów reguły zachowywania. Pamiętaj, aby używać wygenerowanych nazw w języku Java do określania typów, jeśli różnią się one od kodu źródłowego w Kotlinie.
Typy proste
Aby określić typ prosty, użyj jego słowa kluczowego w języku Java. R8 rozpoznaje te typy proste: boolean
, byte
, short
, char
, int
, long
, float
, double
.
Przykładowa reguła z typem prostym:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Typy ogólne
Podczas kompilacji kompilator Kotlin/Java usuwa informacje o typach ogólnych, więc podczas pisania reguł keep, które obejmują typy ogólne, musisz kierować je na skompilowaną reprezentację kodu, a nie na oryginalny kod źródłowy. Więcej informacji o tym, jak zmieniają się typy ogólne, znajdziesz w artykule Wymazywanie typów.
Jeśli na przykład masz ten kod z nieograniczonym typem ogólnym zdefiniowanym w Box.kt
:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Po wymazaniu typu znak T
jest zastępowany znakiem Object
. Aby zachować konstruktor i metodę klasy, reguła musi używać java.lang.Object
zamiast ogólnego T
.
Przykładowa reguła przechowywania:
# 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();
}
Jeśli masz ten kod z ograniczonym typem ogólnym w NumberBox.kt
:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
W tym przypadku wymazywanie typu zastępuje T
jego ograniczeniem java.lang.Number
.
Przykładowa reguła przechowywania:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Jeśli jako klasy bazowej używasz ogólnych typów specyficznych dla aplikacji, musisz też uwzględnić reguły zachowywania dla klas bazowych.
Na przykład w przypadku tego kodu:
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) {
}
Możesz użyć reguły zachowywania z includedescriptorclasses
, aby zachować zarówno klasę UnpackOptions
, jak i metodę klasy Box
za pomocą jednej reguły w ten sposób:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Aby zachować konkretną funkcję, która przetwarza listę obiektów, musisz napisać regułę, która dokładnie pasuje do sygnatury funkcji. Pamiętaj, że typy ogólne są usuwane, więc parametr taki jak List<Product>
jest traktowany jako java.util.List
.
Załóżmy, że masz klasę narzędziową z funkcją, która przetwarza listę obiektów Product
w ten sposób:
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)
Aby chronić tylko funkcję processProducts
, możesz użyć tej reguły zachowywania:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Typy tablic
Określ typ tablicy, dodając znak []
do typu komponentu dla każdego wymiaru tablicy. Dotyczy to zarówno typów klas, jak i typów prostych.
- Jednowymiarowa tablica klas:
java.lang.String[]
- Dwuwymiarowa tablica typów prostych:
int[][]
Jeśli na przykład masz ten kod:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Możesz użyć tej reguły przechowywania:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Symbole wieloznaczne
W tabeli poniżej pokazujemy, jak używać symboli wieloznacznych, aby stosować reguły zachowywania do wielu klas lub elementów, które pasują do określonego wzorca.
Symbol wieloznaczny | Dotyczy zajęć lub członków | Opis |
---|---|---|
** | Obie opcje | Najczęściej używany. Odpowiada dowolnej nazwie typu, w tym dowolnej liczbie separatorów pakietów. Jest to przydatne do dopasowywania wszystkich klas w pakiecie i jego podpakietach. |
* | Obie opcje | W przypadku specyfikacji klasy pasuje do dowolnej części nazwy typu, która nie zawiera separatorów pakietów (. ). W przypadku specyfikacji elementu pasuje do dowolnej nazwy metody lub pola. Użyty samodzielnie jest też aliasem dla ** . |
? | Obie opcje | Wskazuje dopasowanie do dowolnego pojedynczego znaku w nazwie klasy lub elementu. |
*** | Wspierający | Pasuje do dowolnego typu, w tym typów prostych (np. int ), typów klas (np. java.lang.String ) i typów tablic o dowolnym wymiarze (np. byte[][] ). |
… | Wspierający | Dopasowuje dowolną listę parametrów metody. |
% | Wspierający | Pasuje do dowolnego typu prostego (np. `int`, `float`, `boolean` itp.). |
Oto kilka przykładów użycia specjalnych symboli wieloznacznych:
Jeśli masz kilka metod o tej samej nazwie, które przyjmują różne typy proste jako dane wejściowe, możesz użyć
%
, aby napisać regułę zachowywania, która zachowa wszystkie te metody. Na przykład ta klasaDataStore
ma kilka metodsetValue
:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
Ta zasada przechowywania zachowuje wszystkie metody:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
Jeśli masz kilka klas, których nazwy różnią się jednym znakiem, użyj
?
, aby utworzyć regułę zachowywania, która zachowa wszystkie te klasy. Jeśli na przykład masz te klasy:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
Ta reguła przechowywania zachowuje wszystkie zajęcia:
-keep class com.example.models.UserV?
Aby dopasować klasy
Example
iAnotherExample
(jeśli byłyby klasami najwyższego poziomu), ale niecom.foo.Example
, użyj tej reguły zachowywania:-keep class *Example
Jeśli użyjesz samego symbolu *, będzie on działać jako alias dla **. Na przykład poniższe reguły przechowywania są równoważne:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Sprawdzanie wygenerowanych nazw w Javie
Podczas pisania reguł zachowywania musisz określić klasy i inne typy odwołań, używając ich nazw po skompilowaniu do kodu bajtowego Javy (przykłady znajdziesz w specyfikacji klasy i typów). Aby sprawdzić, jakie nazwy Java zostały wygenerowane dla Twojego kodu, użyj jednego z tych narzędzi w Android Studio:
- APK Analyzer
- Otwórz plik źródłowy Kotlin i sprawdź kod bajtowy, klikając Narzędzia > Kotlin > Pokaż kod bajtowy Kotlin > Dekompiluj.