Dodawanie reguł zachowania

Ogólnie rzecz biorąc, reguła zachowywania określa klasę (lub podklasę lub implementację), a następnie elementy członkowskie – metody, konstruktory lub pola – w tej klasie, które mają zostać zachowane.

Ogólna składnia reguły przechowywania jest następująca:


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

Poniżej znajdziesz przykład reguły zachowywania, w której jako opcji zachowywania użyto keepclassmembers, jako modyfikatora – allowoptimization, a zachowywane są wartości someSpecificMethod() z 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 zachowania Opis
keepclassmembers Zachowuje określonych członków tylko wtedy, gdy R8 nie usunie klasy, która ich zawiera.
keep Zachowuje określone klasy i określonych członków (pola i metody), uniemożliwiając ich optymalizację.

Uwaga: keep należy zwykle używać tylko z modyfikatorami opcji keep, ponieważ keep samo w sobie uniemożliwia optymalizację dopasowanych klas.
keepclasseswithmembers Zachowuje klasę i jej określonych członków tylko wtedy, gdy klasa zawiera wszystkich członków z jej specyfikacji.
keepclassmembernames Zapobiega zmianie nazw określonych elementów klasy, ale nie uniemożliwia usunięcia klasy ani jej elementów.

Uwaga: znaczenie tej opcji jest często błędnie rozumiane. Zamiast niej używaj równoważnej opcji -keepclassmembers,allowshrinking.
keepnames Zapobiega zmianie nazw 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 rozumiane. 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.

Wybierz odpowiednią opcję przechowywania

Wybór odpowiedniej opcji zachowania ma kluczowe znaczenie dla określenia właściwej optymalizacji aplikacji. Niektóre opcje zachowania zmniejszają rozmiar kodu (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 zachowania Zmniejsza klasy zaciemnia klasy, Zmniejsza rozmiar członków Maskowanie członków
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

Modyfikator opcji Zachowaj

Modyfikator opcji zachowywania służy do kontrolowania zakresu i działania reguły zachowywania. Do reguły zachowywania możesz dodać 0 lub więcej modyfikatorów opcji zachowywania.

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.
allowobfuscation Umożliwia zmianę nazw określonych elementów. Elementy nie są jednak usuwane ani optymalizowane w inny sposób.
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 poszerzenie) 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 zajęć

W każdej regule zachowywania musisz określić klasę (w tym interfejs, wyliczenie i klasy adnotacji). Opcjonalnie możesz ograniczyć regułę na podstawie adnotacji, określając superklasę lub zaimplementowany interfejs albo modyfikator dostępu do klasy. Wszystkie klasy, w tym klasy z przestrzeni nazw java.lang, takie jak java.lang.String, muszą być podane przy użyciu pełnej i jednoznacznej nazwy Java. Aby dowiedzieć się, jakich nazw należy używać, sprawdź kod bajtowy za pomocą narzędzi opisanych w sekcji Sprawdzanie wygenerowanych nazw w języku Java.

Poniższy przykład pokazuje, jak należy określić klasę MaterialButton:

  • Prawidłowa: com.google.android.material.button.MaterialButton
  • Nieprawidłowy: MaterialButton

Specyfikacje klas określają też elementy w klasie, które powinny zostać zachowane. Na przykład ta reguła zachowuje klasę MyClass i metodę someSpecificMethod():

-keep class com.example.MyClass {
  void someSpecificMethod();
}

Określanie klas na podstawie adnotacji

Aby określić klasy na podstawie ich adnotacji, dodaj przed w pełni kwalifikowaną nazwą Java adnotacji symbol @. Przykład:

-keep class @com.example.MyAnnotation com.example.MyClass

Jeśli reguła przechowywania ma więcej niż jedną adnotację, zachowuje klasy, które mają wszystkie wymienione adnotacje. Możesz podać wiele adnotacji, ale reguła jest stosowana tylko wtedy, gdy klasa ma wszystkie wymienione adnotacje. Na przykład poniższa reguła zachowuje wszystkie klasy oznaczone adnotacjami zarówno przez Annotation1, jak i Annotation2.

-keep class @com.example.Annotation1 @com.example.Annotation2 *

Określanie podklas i implementacji

Aby kierować reklamy na podklasę lub klasę, która implementuje interfejs, użyj odpowiednio symboli extend i implements.

Jeśli na przykład masz klasę Bar z podklasą Foo w ten sposób:

class Foo : Bar()

Ta reguła zachowywania obejmuje 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 interfejs Bar:

class Foo : Bar

Poniższa zasada zachowywania zachowuje wszystkie klasy, które implementują interfejs Bar. Pamiętaj, że reguła zachowywania nie obejmuje samego interfejsu Bar.

-keep class * implements Bar

Określanie klas na podstawie modyfikatorów dostępu

Aby zwiększyć precyzję reguł przechowywania, możesz określić modyfikatory dostępu, takie jak public, private, static i final.

Na przykład ta reguła zachowuje wszystkie klasy public w pakiecie api i jego podpakietach oraz wszystkie publiczne i chronione elementy w tych klasach.

-keep public class com.example.api.** { public protected *; }

Możesz też używać modyfikatorów w przypadku elementó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 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 val 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 kluczowego public, jak pokazano w tym przykładzie:

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • Gdy kompilowany jest element suspend, dopasuj jego skompilowany podpis kodu bajtowego w regule keep.

    Rozważmy na przykład konkretną klasę repozytorium w Kotlinie zdefiniowaną w ten sposób:

    package com.example.repository
    
    import com.example.model.User
    
    class UserRepository {
        suspend fun fetchUser(id: String): User {
            // Implementation details...
        }
    }
    

    Gdy kompilator Kotlin kompiluje tę klasę do kodu bajtowego, funkcje suspendprzechodzą transformację w stylu przekazywania kontynuacji (CPS). Modyfikator suspend jest usuwany, zwracany typ jest zmieniany na java.lang.Object, a do sygnatury metody jest dołączany parametr kotlin.coroutines.Continuation, który zarządza asynchronicznym automatem stanów.

    Skompilowany podpis metody w kodzie bajtowym wygląda tak:

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    Aby napisać regułę zachowywania dla tej funkcji, nie możesz używać składni języka Kotlin suspend. Zamiast tego musisz dopasować skompilowany podpis kodu bajtowego (szczególnie odwołując się do kotlin.coroutines.Continuation) lub użyć ....

    Przykład pasujący do dokładnego skompilowanego podpisu kodu bajtowego:

    -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 w klasie, reguła nie będzie stosowana do innych użytkowników.

Określanie członków na podstawie adnotacji

Możesz określić członków na podstawie ich adnotacji. Podobnie jak w przypadku klas, przed pełną i jednoznaczną nazwą kwalifikowaną adnotacji w języku Java umieszcza się znak @. Pozwala to zachować tylko te elementy w klasie, które są oznaczone określonymi adnotacjami. Jeśli na przykład chcesz zachować metody i pola oznaczone adnotacją @com.example.MyAnnotation:

-keep class com.example.MyClass {
  @com.example.MyAnnotation <methods>;
  @com.example.MyAnnotation <fields>;
}

Możesz połączyć to z dopasowywaniem adnotacji na poziomie klasy, aby tworzyć zaawansowane reguły kierowane na konkretne elementy:

-keep class @com.example.ClassAnnotation * {
  @com.example.MethodAnnotation <methods>;
  @com.example.FieldAnnotation <fields>;
}

Dzięki temu zachowasz klasy z adnotacją @ClassAnnotation, a w nich metody z adnotacją @MethodAnnotation i pola z adnotacją @FieldAnnotation.

W miarę możliwości używaj reguł przechowywania opartych na adnotacjach. Takie podejście zapewnia wyraźne powiązanie między kodem a regułami przechowywania i często prowadzi do bardziej niezawodnych konfiguracji. Na przykład biblioteka adnotacji androidx.annotation korzysta z tego mechanizmu.

Metody

Składnia określania metody w specyfikacji elementu reguły zachowywania jest następująca:

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

Na przykład poniższa reguła zachowywania zachowuje publiczną metodę o nazwie getUserId(), która zwraca wartość String.

-keep class com.example.model.UserData {
    public java.lang.String getUserId();
}

Możesz użyć znaku <methods> jako skrótu, aby dopasować wszystkie metody w klasie w następujący sposób:

-keep class com.example.model.UserData {
    <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 następująca:

[<access_modifier>] <init>(parameter_types);

Na przykład ta reguła zachowywania zachowuje konstruktor obiektu stanu interfejsu, który przyjmuje instancję repozytorium.

-keep class com.example.ui.state.UserViewModel {
    public <init>(com.example.repository.UserDataRepository);
}

Aby zachować wszystkie publiczne konstruktory, skorzystaj z tego przykładu:

-keep class com.example.ui.state.UserViewModel {
    public <init>(...);
}

Pola

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 liczby całkowitej o nazwie STATUS_ACTIVE:

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

Możesz użyć znaku <fields> jako skrótu, aby dopasować wszystkie pola w klasie w ten sposób:

-keep class com.example.models.User {
    <fields>;
}

Typy

W tej sekcji opisujemy, jak określać typy zwracanych wartości, typy parametrów i typy pól w specyfikacjach elementów reguł 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.

Oto 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 sekcji 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, w regule musisz użyć znaku java.lang.Object zamiast ogólnego znaku 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, czyli java.lang.Number.

Przykładowa reguła przechowywania:

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

Jeśli jako klasy bazowe 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();
}

Przykłady

Aby na przykład zachować konkretną klasę i wszystkie jej elementy, użyj tego kodu:

-keep class com.myapp.MyClass { *; }

Aby zachować tylko klasę wraz z jej domyślnym konstruktorem, ale bez innych elementów, użyj tego kodu:

-keep class com.myapp.MyClass

Zalecamy, aby zawsze określać niektórych członków. Na przykład poniższy kod zachowuje publiczne pole text i publiczną metodę updateText() w klasie MyClass.

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

Aby zachować wszystkie pola i metody publiczne, zapoznaj się z tym przykładem:

-keep public class com.example.api.ApiClient {
    public *;
}

Pomiń specyfikację członka

Pominięcie specyfikacji elementu powoduje, że R8 zachowuje domyślny konstruktor klasy.

Jeśli na przykład wpiszesz -keep class com.example.MyClass lub -keep class com.example.MyClass {}, R8 potraktuje to tak, jakbyś wpisał(-a) to:

-keep class com.example.MyClass{
  void <init>();
}

Wykluczanie wzorców nazw członków

Od wersji 9.2.0 wtyczki Androida do obsługi Gradle (AGP) możesz negować wzorce nazw elementów w regułach zachowywania. Umożliwia to określenie członków, których chcesz zachować lub wykluczyć na podstawie wzorców. Aby zanegować wzorzec, dodaj przed wzorcem nazwy elementu wykrzyknik (!).

Możesz użyć tej funkcji w sytuacjach, w których chcesz zachować większość elementów pasujących do szerszego wzorca, ale wykluczyć konkretne elementy, np. metody przeznaczone tylko do testowania.

Przykład:

Aby zachować wszystkie metody publiczne w com.example.MyClass z wyjątkiem tych, które kończą się ciągiem znaków „ForTesting”, użyj tej reguły:

-keepclassmembers class com.example.MyClass {
    public *** !*ForTesting(...);
}

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 dodanym sufiksem 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);
}

Symbole wieloznaczne

W tabeli poniżej pokazujemy, jak za pomocą symboli wieloznacznych stosować reguły zachowywania do wielu klas lub elementów, które pasują do określonego wzorca.

Wildcard Dotyczy zajęć lub użytkowników Opis
** Oba Najczęściej używane. Pasuje do dowolnej nazwy typu, w tym do dowolnej liczby separatorów pakietów. Jest to przydatne do dopasowywania wszystkich klas w pakiecie i jego podpakietach.
* Oba 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żywana samodzielnie jest też aliasem dla **.
? Oba Odpowiada dowolnemu pojedynczemu znakowi w nazwie klasy lub elementu.
*** Członkowie 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[][]).
… Członkowie Pasuje do dowolnej listy parametrów metody.
% Członkowie 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 klasa DataStore ma kilka metod setValue:

    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 symbolu ?, 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 zachowywania zachowuje wszystkie klasy:

    -keep class com.example.models.UserV?
    
  • Aby dopasować klasy Example i AnotherExample (jeśli byłyby klasami najwyższego poziomu), ale nie com.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 te reguły zachowywania są równoważne:

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

Reguły pozostawienia warunkowego

Oprócz standardowych reguł przechowywania możesz używać warunkowych reguł przechowywania, które są stosowane tylko wtedy, gdy spełniony jest określony warunek. Możesz określić reguły warunkowe za pomocą flagi -if. Reguła zachowywania, która następuje po fladze -if, jest aktywna tylko wtedy, gdy specyfikacja klasy we fladze -if ma dopasowanie.

Reguły warunkowego zachowywania są szczególnie przydatne w przypadku bibliotek lub wzorców kodu, które używają odbicia, gdy reguła zachowywania jest potrzebna tylko wtedy, gdy występuje określona klasa lub element albo pasuje do wzorca. Stosowanie reguł warunkowych pomaga zminimalizować rozmiar aplikacji, ponieważ zapobiega niepotrzebnemu przechowywaniu kodu.

Ogólna składnia warunkowej reguły przechowywania jest taka:

-if <class_specification_if> <keep_rule>

Jeśli specyfikacja klasy w -ifwarunku zawiera symbole wieloznaczne (np. * lub **), sekwencja znaków pasująca do symbolu wieloznacznego jest przechwytywana. Do przechwyconych ciągów znaków możesz się odwoływać w kolejnej regule zachowywania za pomocą odwołań wstecznych: <1> odnosi się do ciągu znaków przechwyconego przez pierwszy symbol wieloznaczny, <2> odnosi się do ciągu znaków przechwyconego przez drugi symbol wieloznaczny itd.

Na przykład komponent nawigacji Jetpack generuje klasy NavArgs do bezpiecznego przekazywania argumentów między miejscami docelowymi. Gdy używasz delegata NavArgsLazy, do wyszukiwania i wywoływania statycznej metody fromBundle w wygenerowanej klasie NavArgs w celu deserializacji argumentów używa odbicia. Jeśli Twoja aplikacja korzysta z NavArgs, musisz zachować tylko metodę fromBundle w przypadku klas, które implementują interfejs NavArgs.

Możesz użyć warunkowej reguły zachowywania, aby określić, że jeśli jakakolwiek klasa implementuje androidx.navigation.NavArgs, R8 ma zachować metodę fromBundle dla tej konkretnej klasy:

# 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);
}

W tym przykładzie ** to pierwszy i jedyny symbol wieloznaczny w warunku -if. Pasuje do nazwy klasy dowolnej klasy implementującej androidx.navigation.NavArgs. Ciąg znaków, który **pasuje (w tym przypadku nazwa klasy), jest przechwytywany i możesz się do niego odwoływać za pomocą <1> w kolejnej regule. Reguła -keepclassmembers ma zastosowanie do każdej klasy implementującej androidx.navigation.NavArgs, która została dopasowana przez warunek -if. Jeśli R8 nie znajdzie żadnych klas implementujących NavArgs, zignoruje tę regułę zachowywania.

Inne typowe zastosowanie to biblioteki serializacji JSON, takie jak Gson. Jeśli klasy modelu danych używają adnotacji @SerializedName Gson w dowolnym polu, możesz użyć reguły warunkowej, aby chronić każdą taką klasę i jej elementy, których Gson potrzebuje do odbicia:

# 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>(...);
}

Odwołania wsteczne przechwytują ciągi znaków, które mogą być podciągami nazw klas, jeśli symbol wieloznaczny pasuje tylko do części nazwy. Jeśli na przykład użyjesz znaku -if class com.example.*X*, R8 przechwyci podciąg przed znakiem X jako <1>, a podciąg po znaku X jako <2>. Poniższa reguła używa tego do znajdowania dowolnej nazwy klasy zawierającej X i zachowywania odpowiedniej klasy, w której X jest zastępowane przez Y:

# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>

Reguły pozostawienia warunkowego dotyczące odbicia

Jednym z typowych zastosowań warunkowych reguł zachowywania jest obsługa odbicia, w którym określone metody lub klasy są dostępne dynamicznie w czasie działania programu. Jeśli na przykład biblioteka używa odbicia do interakcji z Twoim kodem, możesz potrzebować tylko niektórych elementów, jeśli używasz określonej funkcji tej biblioteki.

Biblioteka Jetpack Navigation używa odbicia za pomocą delegata NavArgsLazy do wywoływania statycznej metody fromBundle w wygenerowanych klasach NavArgs w celu bezpiecznego typowo przekazywania argumentów. Aby mieć pewność, że ta metoda jest zachowywana tylko w przypadku implementacji NavArgs, a nie w przypadku każdej klasy, biblioteka Jetpack Navigation zawiera to warunkowe regułę zachowywania:

# 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);
}

Ta reguła zachowuje fromBundle tylko w przypadku zajęć, które tego wymagają, zamiast zachowywać ją we wszystkich zajęciach lub wymagać ręcznego określania, które zajęcia tego potrzebują.

Sprawdzanie wygenerowanych nazw w Javie

Podczas pisania reguł zachowywania musisz określać klasy i inne typy odwołań, używając ich nazw po skompilowaniu do kodu bajtowego Javy (przykłady znajdziesz w sekcjach Specyfikacja klasy i Typy). Aby sprawdzić, jakie nazwy w języku Java zostały wygenerowane dla Twojego kodu, użyj jednego z tych narzędzi w Androidzie Studio:

  • Analizator APK
  • Otwórz plik źródłowy Kotlin i sprawdź kod bajtowy, klikając Narzędzia > Kotlin > Pokaż kod bajtowy Kotlin > Dekompiluj.