Dodawanie reguł zachowania

Ogólnie rzecz biorąc, reguła zachowania określa klasę (lub podklasę albo 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:


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

Poniżej znajdziesz przykład reguły przechowywania, która używa keepclassmembers jako opcji przechowywania, allowoptimization jako modyfikatora i przechowuje someSpecificMethod() z com.example.MyClass:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

Opcja zachowania

Opcja przechowywania to pierwsza część reguły przechowywania. Określa, które aspekty klasy mają zostać zachowane. Dostępnych jest 6 różnych opcji zachowywania: keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames i keepclasseswithmembernames.

W tabeli poniżej znajdziesz opis tych opcji przechowywania:

Opcja zachowania Opis
keepclassmembers Zachowuje określone elementy tylko wtedy, gdy R8 nie usunie klasy, która je zawiera.
keep Zachowuje określone klasy i określone elementy (pola i metody), zapobiegając ich optymalizacji.

Uwaga: reguły keep należy zasadniczo używać tylko z modyfikatorami opcji keep, ponieważ sama reguła keep uniemożliwia optymalizację dopasowanych klas.
keepclasseswithmembers Zachowuje klasę i jej określone elementy tylko wtedy, gdy klasa zawiera wszystkie elementy ze specyfikacji klasy.
keepclassmembernames Zapobiega zmianie nazwy określonych elementów klasy, ale nie zapobiega usunięciu 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ę zachowania

Wybór odpowiedniej opcji zachowania jest kluczowy dla określenia właściwej optymalizacji aplikacji. Niektóre opcje zachowania zmniejszają kod, co polega na usunięciu kodu, do którego nie ma odwołań, podczas gdy inne zaciemniają lub zmieniają nazwę kodu. W tabeli poniżej znajdziesz opis działania poszczególnych opcji przechowywania:

Opcja zachowania Zmniejsza klasy Zaciemnia klasy Zmniejsza rozmiar członków Zaciemnia elementy
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

Modyfikator opcji zachowania

Modyfikator opcji zachowywania służy do kontrolowania zakresu i działania reguły zachowywania. Do reguły przechowywania możesz dodać co najmniej 1 modyfikator 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 usunięte ani nie zmienią się ich nazwy.
allowobfuscation Umożliwia zmianę nazwy określonych elementów. Elementy nie są jednak usuwane ani w żaden sposób optymalizowane.
allowshrinking Umożliwia usunięcie określonych elementów, jeśli R8 nie znajdzie do nich odwołań. Elementy nie są jednak zmieniane ani w żaden inny sposób optymalizowane.
includedescriptorclasses Instruuje R8, aby zachował wszystkie klasy, które pojawiają się w deskryptorach metod (typy parametrów i zwracane typy) i pól (typy pól), które są zachowywane.
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 R8 przenoszenie klas do różnych pakietów, w tym do pakietu domyślnego (głównego).

Specyfikacja klasy

W każdej regule zachowania musisz określić klasę (w tym interfejs, typ wyliczeniowy i klasy adnotacji). Opcjonalnie możesz ograniczyć regułę na podstawie adnotacji, określając klasę nadrzędną 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 nazwy Java. Aby dowiedzieć się, jakich nazw należy używać, sprawdź kod bajtowy za pomocą narzędzi opisanych w sekcji Sprawdzanie wygenerowanych nazw Java.

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

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

Specyfikacje klas określają też członków w klasie, którzy powinni zostać zachowani. 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, przed pełną i jednoznaczną nazwą adnotacji w języku Java dodaj symbol @. Przykład:

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

Jeśli reguła zachowania ma więcej niż 1 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 ta reguła zachowuje wszystkie klasy z adnotacjami Annotation1 i Annotation2.

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

Określanie podklas i implementacji

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

Jeśli na przykład masz klasę Bar z podklasą Foo:

class Foo : Bar()

Ta reguła zachowywania obejmuje wszystkie podklasy klasy Bar. Pamiętaj, że reguła zachowania nie obejmuje samej klasy nadrzędnej Bar.

-keep class * extends Bar

Jeśli masz klasę Foo, która implementuje interfejs Bar:

class Foo : Bar

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

-keep class * implements Bar

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

Aby reguły zachowywania były bardziej precyzyjne, 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 dla elementów w klasie. Na przykład ta 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 Kotlina, takich jak internal i suspend. Aby zachować te 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 skompilowany zostanie element suspend, dopasuj jego skompilowaną sygnaturę kodu bajtowego w regule zachowywania.

    Rozważmy na przykład konkretną klasę repozytorium zdefiniowaną w języku Kotlin 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 suspend przechodzą transformację Continuation-Passing Style (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 asynchroniczną maszyną 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ć skompilowaną sygnaturę kodu bajtowego (odwołującą się konkretnie do kotlin.coroutines.Continuation) lub użyć ....

    Oto przykład pasujący do dokładnej skompilowanej sygnatury 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 użytkownika

Specyfikacja klasy może opcjonalnie zawierać elementy klasy, które mają zostać zachowane. Jeśli określisz co najmniej 1 użytkownika dla klasy, reguła nie będzie dotyczyć innych użytkowników.

Określanie członków na podstawie adnotacji

Możesz określić użytkowników na podstawie ich adnotacji. Podobnie jak w przypadku klas, przed pełną i jednoznaczną nazwą adnotacji w języku Java umieszcza się znak @. Dzięki temu możesz zachować tylko te elementy klasy, które są oznaczone określonymi adnotacjami. Aby na przykład zachować metody i pola z 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 klasy oznaczone jako @ClassAnnotation zostaną zachowane, a w tych klasach zostaną zachowane metody oznaczone jako @MethodAnnotation i pola oznaczone jako @FieldAnnotation.

W miarę możliwości rozważ użycie reguł przechowywania opartych na adnotacjach. Takie podejście zapewnia wyraźne powiązanie kodu z regułami zachowywania i często prowadzi do bardziej niezawodnych konfiguracji. Ten mechanizm jest używany na przykład przez bibliotekę adnotacji androidx.annotation.

Do refleksji we własnym kodzie możesz też użyć Jetpack Keep. Zamiast pisać reguły zachowania, które są kierowane na adnotacje, dodajesz adnotacje do kodu, a wtyczka Gradle generuje reguły zachowania.

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 ta reguła zachowywania zachowuje metodę publiczną o nazwie getUserId(), która zwraca String.

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

Możesz użyć <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 zwracanych typów i typów parametrów znajdziesz w sekcji Typy.

Zespoły

Aby określić konstruktor, użyj <init>. Składnia służąca do określania konstruktora w specyfikacji elementu reguły zachowania jest następująca:

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

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

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

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

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

Pola

Składnia służąca do 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 ciągu 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ć zwracane typy, typy parametrów i typy pól w specyfikacjach elementów reguły zachowania. 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 języku Kotlin.

Typy podstawowe

Aby określić typ prosty, użyj jego słowa kluczowego w języku Java. R8 rozpoznaje te typy podstawowe: boolean, byte, short, char, int, long, float, double.

Oto przykładowa reguła z typem podstawowym:

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

Typowy rodzaj

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 o wymazywaniu 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 wygląda tak:

# 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 typów zastępuje T jego powiązaniem, czyli java.lang.Number.

Przykładowa reguła przechowywania wygląda tak:

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

Jeśli jako klasy bazowej używasz typów ogólnych 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 zachowania 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ć określoną 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 postrzegany jako java.util.List.

Jeśli na przykład masz klasę pomocniczą 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 zachowania:

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

Typy tablic

Określ typ tablicy, dodając [] do typu komponentu dla każdego wymiaru tablicy. Dotyczy to zarówno typów klas, jak i typów podstawowych.

  • Jednowymiarowa tablica klas: java.lang.String[]
  • Dwuwymiarowa tablica podstawowa: int[][]

Jeśli na przykład masz taki 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 nie inne elementy, użyj tego:

-keep class com.myapp.MyClass

Zalecamy, aby zawsze określać niektórych użytkownikó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 składowej powoduje, że R8 zachowuje domyślny konstruktor klasy.

Jeśli na przykład napiszesz -keep class com.example.MyClass lub -keep class com.example.MyClass {}, R8 potraktuje je tak, jakby napisano:

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

Neguj wzorce nazw elementó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 wykrzyknik (!) przed wzorcem nazwy elementu.

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 oprócz tych, które kończą się na „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ą (powszechnie nazywanej funkcją najwyższego poziomu), użyj wygenerowanej nazwy Java dla klasy dodanej niejawnie 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łę zachowania 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 używać symboli wieloznacznych, aby 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. Dopasowuje dowolną nazwę typu, w tym dowolną liczbę separatorów pakietów. Jest to przydatne do dopasowywania wszystkich klas w pakiecie i jego podpakietach.
* Oba W przypadku specyfikacji klas dopasowuje dowolną część nazwy typu, która nie zawiera separatorów pakietów (.).
W przypadku specyfikacji elementów dopasowuje dowolną nazwę 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 podstawowych (takich jak int), typów klas (takich jak java.lang.String) i typów tablic o dowolnym wymiarze (takich jak 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 reguła zachowywania zachowuje wszystkie metody:

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • Jeśli masz kilka klas o nazwach różniących się o 1 znak, użyj ?, aby napisać regułę zachowywania, która zachowa je wszystkie. Jeśli na przykład masz te zajęcia:

    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ły to klasy na poziomie głównym), ale nie com.foo.Example, użyj tej reguły zachowywania:

    -keep class *Example
    
  • Jeśli użyjesz samego znaku *, będzie on działać jako alias dla **. Na przykład te 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[];) }
    

Reguły warunkowego zachowywania

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

Warunkowe reguły zachowywania są szczególnie przydatne w przypadku bibliotek lub wzorców kodu, które używają refleksji, gdy reguła zachowywania jest potrzebna tylko wtedy, gdy określona klasa lub element jest obecny lub pasuje do wzorca. Używanie reguł warunkowych pomaga zminimalizować rozmiar aplikacji, zapobiegając niepotrzebnemu przechowywaniu kodu.

Ogólna składnia reguły warunkowej zachowywania wygląda tak:

-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 tych przechwyconych ciągów znaków możesz się odwoływać w kolejnych regułach przechowywania 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, używa on refleksji, aby znaleźć i wywołać statyczną metodę fromBundle w wygenerowanej klasie NavArgs w celu deserializacji argumentów. 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 ** jest pierwszym i jedynym symbolem wieloznacznym w warunku -if. Pasuje do nazwy klasy dowolnej klasy implementującej androidx.navigation.NavArgs. Ciąg znaków, który pasuje do ** (w tym przypadku nazwa klasy), jest przechwytywany i można się do niego odwoływać za pomocą <1> w kolejnej regule. Reguła -keepclassmembers ma więc 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.

Innym typowym przypadkiem użycia są 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ć podłańcuchami nazw klas, jeśli symbol wieloznaczny pasuje tylko do części nazwy. Jeśli na przykład używasz -if class com.example.*X*, R8 przechwytuje podłańcuch przed X jako <1>, a podłańcuch po X jako <2>. Ta reguła używa tego elementu, aby znaleźć dowolną nazwę klasy zawierającą X i zachować odpowiednią klasę, w której X jest zastąpione 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 przypadku którego określone metody lub klasy są dostępne dynamicznie w czasie działania programu. Jeśli na przykład biblioteka używa refleksji do interakcji z kodem, może być konieczne zachowanie tylko niektórych elementów, jeśli używasz określonej funkcji tej biblioteki.

Biblioteka Jetpack Navigation używa refleksji za pomocą delegata NavArgsLazy, aby wywołać statyczną metodę fromBundle w wygenerowanych klasach NavArgs na potrzeby przekazywania argumentów z zabezpieczeniami wpisywania. Aby mieć pewność, że ta metoda jest zachowywana tylko w przypadku implementacji NavArgs, a nie każdej klasy, Jetpack Navigation zawiera tę regułę warunkowego 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ć go we wszystkich zajęciach lub wymagać ręcznego określenia, które zajęcia tego wymagają.

Sprawdzanie wygenerowanych nazw w języku Java

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 Android Studio:

  • APK Analyzer
  • Otwórz plik źródłowy Kotlin i sprawdź kod bajtowy, wybierając Tools > Kotlin > Show Kotlin Bytecode > Decompile (Narzędzia > Kotlin > Pokaż kod bajtowy Kotlin > Dekompiluj).