Generowanie reguł zachowywania na podstawie adnotacji

Jetpack Keep umożliwia deklarowanie użycia odbicia i interfejsu Java Native Interface (JNI) za pomocą adnotacji bezpośrednio w kodzie źródłowym Kotlin lub Java, a nie w osobnych plikach reguł zachowywania. Wtyczka Jetpack Keep do Gradle odczytuje te adnotacje w czasie kompilacji i generuje reguły zachowywania, których potrzebuje R8.

Jetpack Keep pomaga w utrzymaniu skuteczności optymalizacji R8 w miarę zmian w bazie kodu:

  • Reguły zachowywania znajdują się obok kodu, który ich potrzebuje. Gdy zmienisz nazwę lub usuniesz kod, który używa odbicia, zaktualizujesz adnotację w tym samym miejscu. Nie ma osobnego pliku reguł, który trzeba synchronizować.

  • Wygenerowane reguły są warunkowe. R8 zachowuje cel wywołania refleksyjnego tylko wtedy, gdy zachowuje też kod, który to wywołanie wykonuje. Jeśli kod wywołujący jest nieużywany i R8 go usuwa (co często zdarza się w przypadku opcjonalnych funkcji biblioteki), R8 może też usunąć cel, dzięki czemu reguły z nieużywanych funkcji nie zwiększają rozmiaru aplikacji.

  • Lint znajduje odbicie. Biblioteka zawiera kontrole lint, które oznaczają wywołania refleksyjne bez adnotacji i mogą automatycznie dodawać wiele adnotacji.

Jak działa Jetpack Keep

Rozważmy funkcję, która tworzy instancję klasy według nazwy. Jeśli R8 nie może stwierdzić, że Twój kod używa klasy, usuwa konstruktor klasy, a aplikacja ulega awarii w czasie działania. Aby zapobiec awarii, możesz dodać do pliku reguł zachowywania regułę taką jak ta:

# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
    <init>();
}

W przypadku Jetpack Keep dodajesz adnotację do funkcji, która wykonuje odbicie:

// ResourceLocatorImpl isn't on this module's compile classpath, so both the
// annotation and the code refer to it by name.
@UsesReflectionToConstruct(
    className = "com.example.ResourceLocatorImpl",
    // Omitting parameterTypes keeps every constructor. An empty array keeps
    // only the no-argument constructor, which the code calls.
    parameterTypes = [],
)
fun loadResourceLocator(): ResourceLocator {
    val implClass = Class.forName("com.example.ResourceLocatorImpl")
    return implClass.getDeclaredConstructor().newInstance() as ResourceLocator
}

W czasie kompilacji wtyczka Gradle generuje regułę zachowywania dla konstruktora. W przeciwieństwie do poprzedniej reguły napisanej ręcznie wygenerowana reguła jest warunkowa: R8 zachowuje konstruktor tylko wtedy, gdy loadResourceLocator pozostaje w zoptymalizowanej aplikacji.

Wymagania

Aby korzystać z Jetpack Keep, Twój projekt musi mieć wtyczkę Androida do obsługi Gradle (AGP) w wersji 8.7 lub nowszej.

Wtyczka Gradle obsługuje te typy modułów:

Typ modułu Wtyczka, do której moduł ma zastosowanie Dodatkowa konfiguracja
Aplikacja na Androida com.android.application Brak
Biblioteka Androida com.android.library Brak
Biblioteka Kotlin Multiplatform z celem Android com.android.kotlin.multiplatform.library Brak
Biblioteka JVM (Java lub Kotlin) java-library lub org.jetbrains.kotlin.jvm Zarejestruj transformację JAR

Konfigurowanie Jetpack Keep

Aby korzystać z Jetpack Keep, dodaj bibliotekę i wtyczkę Gradle do każdego modułu, który używa odbicia lub JNI. Biblioteka udostępnia adnotacje i kontrole lint, a wtyczka przekształca adnotacje w reguły zachowywania.

  1. W pliku libs.versions.toml dodaj bibliotekę i wtyczkę. Biblioteka i wtyczka mają tę samą wersję:

    [versions]
    # The library and the plugin ship together. Use one version for both so
    # that they stay in sync when you upgrade.
    annotationKeep = "1.0.0-alpha01"
    
    [libraries]
    androidx-annotation-keep = { group = "androidx.annotation.keep", name = "keep", version.ref = "annotationKeep" }
    
    [plugins]
    androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }
    

    Gradle pobiera wtyczkę z repozytorium Maven firmy Google, więc upewnij się, że repozytoria pluginManagement w pliku settings.gradle.kts zawierają google().

  2. W głównym pliku build.gradle.kts zadeklaruj wtyczkę obok AGP bez jej stosowania:

    plugins {
        alias(libs.plugins.android.application) apply false
        // Other plugins.
    
        // The plugin relies on AGP classes that it doesn't bundle, so declare
        // it next to AGP. Declaring it here also gives every module the same
        // plugin version.
        alias(libs.plugins.androidx.annotation.keep) apply false
    }
    
  3. W pliku build.gradle.kts każdego modułu, który używa odbicia lub JNI, zastosuj wtyczkę i dodaj zależność biblioteki:

    plugins {
        alias(libs.plugins.android.application)
        // The plugin reads annotations only from this module's own code, so
        // apply it in every module that uses Jetpack Keep.
        alias(libs.plugins.androidx.annotation.keep)
    }
    
    dependencies {
        implementation(libs.androidx.annotation.keep)
    }
    
  4. Zsynchronizuj projekt z plikami Gradle.

W przypadku każdego wariantu kompilacji modułu aplikacji na Androida wtyczka rejestruje zadanie <variant>ExtractKeepRules takie jak releaseExtractKeepRules, i przekazuje wygenerowane reguły do R8 dla tego wariantu. W przypadku modułów biblioteki wtyczka pakuje wygenerowane reguły jako reguły zachowywania dla konsumentów w danych wyjściowych biblioteki. Moduły biblioteki JVM wymagają dodatkowej konfiguracji.

Znajdowanie odbicia za pomocą lint

Jetpack Keep zawiera te kontrole lint:

ReflectionWithoutKeepAnnotations
Oznacza wywołania refleksyjne, takie jak Class.forName, getDeclaredConstructor, getDeclaredMethod i getDeclaredField, które nie mają pasującej adnotacji zachowywania.
MissingKeepAnnotationPlugin
Oznacza moduły, które zależą od androidx.annotation.keep:keep, ale nie stosują wtyczki Gradle androidx.annotation.keep. Bez wtyczki kompilacja nie generuje reguł zachowywania z adnotacji.

Aby użyć lint do dodania adnotacji do modułu Androida, wykonaj te czynności:

  1. Uruchom lint w module, aby znaleźć wywołania refleksyjne, które wymagają adnotacji:

    ./gradlew :app:lint
    
  2. Zastosuj poprawki, które lint może wprowadzić automatycznie:

    ./gradlew :app:lintFix
    

    Lint może automatycznie dodać adnotację, gdy może określić, które klasy i elementy są odzwierciedlane przez kod.

  3. Sprawdź każdą dodaną adnotację, aby potwierdzić, że pasuje do działania kodu. Sprawdź na przykład, czy typy parametrów pasują do konstruktora lub metody, którą wywołuje Twój kod.

  4. Rozwiąż pozostałe problemy w Android Studio. Gdy lint nie może określić klasy docelowej, szybka poprawka wstawia adnotację z symbolem zastępczym TODO, który musisz uzupełnić. Możesz też dodać adnotację ręcznie.

    Na przykład ta funkcja otrzymuje nazwę klasy jako parametr, więc lint nie może określić, którą klasę tworzy. Szybka poprawka dodaje adnotację z symbolem zastępczym:

    @UsesReflectionToConstruct(
        className = TODO(),
        parameterTypes = []
    )
    fun createInstance(className: String): Any =
        Class.forName(className).getDeclaredConstructor().newInstance()
    

    Zastąp symbol zastępczy klasą, którą tworzy Twój kod. Jeśli klasa jest dostępna w czasie kompilacji, użyj classConstant zamiast className. W Kotlinie kod nie zostanie skompilowany, dopóki nie zastąpisz TODO().

  5. Zanim usuniesz istniejące reguły, sprawdź, które reguły zachowywania wtyczka generuje z Twoich adnotacji. W przypadku modułu aplikacji uruchom zadanie wyodrębniania dla wariantu wersji:

    ./gradlew :app:releaseExtractKeepRules
    

    Wtyczka zapisuje reguły w pliku app/build/generated/release/androidx.annotation.keep.rules.pro. W przypadku modułów biblioteki zapoznaj się z sekcją Sprawdzanie wygenerowanych reguł.

  6. Usuń z plików reguł zachowywania reguły, które zastępują nowe adnotacje. Reguła może też obejmować odbicie, którego lint nie oznaczył, np. odbicie w innym module lub w bibliotece, więc przed usunięciem sprawdź każdą regułę:

    • W przypadku reguły obejmującej cały pakiet, np. -keep class com.example.** { *; }, upewnij się, że adnotacje lub inne reguły obejmują wszystkie odbicia w klasach pakietu.
    • W przypadku reguły, która zachowuje określone klasy lub elementy, upewnij się, że ściśle pasuje do jednej z wygenerowanych reguł lub że nie zależy od niej żadne inne odbicie.
  7. Przetestuj kompilację do publikacji.

Dodawanie adnotacji do kodu

Dodaj adnotację Jetpack Keep do kodu, który wykonuje odbicie: funkcji, konstruktora, pola lub klasy zawierającej wywołanie refleksyjne. Adnotacja opisuje cel odbicia, a wygenerowana reguła zachowuje ten cel tylko wtedy, gdy R8 zachowuje kod z adnotacją.

Wybierz adnotację na podstawie działania kodu:

Adnotacja Użyj, gdy Twój kod
@UsesReflectionToConstruct Wywołuje konstruktor za pomocą odbicia, np. za pomocą getDeclaredConstructor().newInstance().
@UsesReflectionToAccessMethod Wywołuje metodę za pomocą odbicia, np. za pomocą getMethod lub getDeclaredMethod.
@UsesReflectionToAccessField Odczytuje lub zapisuje pole za pomocą odbicia, np. za pomocą getField lub getDeclaredField.
@UnconditionallyKeep Jest wywoływany spoza kodu Kotlin lub Java aplikacji, np. z kodu natywnego za pomocą JNI. Używaj tej adnotacji tylko wtedy, gdy nie możesz użyć adnotacji @UsesReflection*, które generują reguły warunkowe.

Wszystkie adnotacje @UsesReflection* są zgodne z tymi konwencjami:

  • Określ klasę docelową za pomocą classConstant lub className. Jeśli klasa jest dostępna w czasie kompilacji, użyj classConstant. Kompilator sprawdza odwołanie, a refaktoryzacje IDE, takie jak zmiana nazwy, aktualizują je. Używaj className tylko wtedy, gdy klasa nie jest dostępna w czasie kompilacji, np. klasa w opcjonalnej zależności. Te 2 parametry wykluczają się wzajemnie.
  • Kieruj na najbardziej konkretną klasę. Wygenerowane reguły zachowują pasujące elementy w określonej klasie oraz w jej podklasach lub implementacjach interfejsu. Im bardziej ogólna klasa, tym więcej kodu zachowuje R8.
  • Określ typy parametrów, aby zachować pojedyncze przeciążenie. Jeśli nie ustawisz parameterTypes ani parameterTypeNames, reguły zachowują wszystkie konstruktory lub wszystkie przeciążenia metody. Aby zachować tylko konstruktor lub metodę bez argumentów, przekaż pustą tablicę.
  • Powtórz adnotację dla wielu celów. Jeśli jedna funkcja odzwierciedla więcej niż 1 klasę lub element, dodaj 1 adnotację dla każdego celu.

Tworzenie obiektów za pomocą odbicia

Użyj @UsesReflectionToConstruct, gdy Twój kod tworzy instancję klasy za pomocą odbicia. W tym przykładzie narzędzie do uruchamiania zadań tworzy instancje implementacji interfejsu StartupTask według nazwy. Ponieważ adnotacja określa interfejs, wygenerowane reguły zachowują konstruktor bez argumentów każdej implementacji StartupTask, o ile R8 zachowuje execute:

interface StartupTask {
    fun run()
}

object TaskRunner {
    // The task class is known only at runtime, so target the interface that
    // every task implements.
    @UsesReflectionToConstruct(classConstant = StartupTask::class, parameterTypes = [])
    fun execute(className: String) {
        val task = Class.forName(className)
            .getDeclaredConstructor()
            .newInstance() as StartupTask
        task.run()
    }
}

Wywoływanie metod za pomocą odbicia

Użyj @UsesReflectionToAccessMethod, gdy Twój kod wywołuje metodę za pomocą odbicia. Ustaw methodName na nazwę metody. W tym przykładzie kod wywołuje metodę statyczną w opcjonalnej zależności. Klasa nie jest dostępna w czasie kompilacji, więc adnotacja używa className zamiast classConstant:

object CrashReporting {
    @UsesReflectionToAccessMethod(
        className = "com.example.crashlib.CrashReporter",
        methodName = "log",
        // Omitting parameterTypes keeps every log overload. Keep only the one
        // that the code calls.
        parameterTypes = [String::class],
    )
    fun log(message: String) {
        val reporterClass = try {
            Class.forName("com.example.crashlib.CrashReporter")
        } catch (e: ClassNotFoundException) {
            // Crash reporting is optional, so skip it instead of failing.
            return
        }
        reporterClass.getMethod("log", String::class.java).invoke(null, message)
    }
}

Dostęp do pól za pomocą odbicia

Użyj @UsesReflectionToAccessField, gdy Twój kod odczytuje lub zapisuje pole za pomocą odbicia. Ustaw fieldName na nazwę pola:

class UserProfileFetcher {
    // User doesn't expose internalId, so reading it requires reflection.
    @UsesReflectionToAccessField(classConstant = User::class, fieldName = "internalId")
    fun readInternalId(user: User): Any? {
        val field = User::class.java.getDeclaredField("internalId")
        field.isAccessible = true
        return field.get(user)
    }
}

Zachowywanie kodu wywoływanego spoza aplikacji

Użyj @UnconditionallyKeep w przypadku kodu, który jest wywoływany spoza kodu Kotlin lub Java aplikacji, np. metody wywoływanej przez kod natywny za pomocą JNI. Dodaj adnotację do wywoływanego elementu, a nie do kodu, który go wywołuje:

class NativeBridge {
    // Only native code calls this method, through a JNI lookup by name that
    // R8 can't see. Keep the method and its name unconditionally.
    @UnconditionallyKeep
    fun onNativeEvent(message: String) {
        Log.d("NativeBridge", "Received from native code: $message")
    }

    // The default proguard-android-optimize.txt file already keeps native
    // method names, so this method doesn't need an annotation.
    external fun startNativeProcess()
}

Wygenerowana reguła zachowuje metodę z adnotacją i jej klasę zawierającą z ich oryginalnymi nazwami. Nie zachowuje innych klas, do których odwołuje się sygnatura metody. Jeśli kod natywny zależy od tych klas, dodaj do nich adnotacje lub reguły zachowywania. Więcej informacji o JNI i R8 znajdziesz w artykule Interakcja z kodem natywnym za pomocą JNI.

Domyślnie @UnconditionallyKeep zachowuje nazwę elementu. Jeśli zewnętrzny wywołujący nie zależy od nazwy, ustaw shouldPreserveName = false, aby R8 mógł zmienić nazwę elementu.

Sprawdzanie wygenerowanych reguł

W tej sekcji wyjaśniamy, jak sprawdzić wygenerowane reguły dla każdego typu modułu. W przypadku modułu aplikacji wtyczka przekazuje reguły bezpośrednio do R8. W przypadku modułu biblioteki wtyczka pakuje reguły jako reguły zachowywania dla konsumentów w danych wyjściowych biblioteki. Aplikacje, które zależą od Twojej biblioteki, automatycznie stosują te reguły podczas uruchamiania R8, nawet jeśli nie używają Jetpack Keep. Więcej informacji o regułach zachowywania dla konsumentów znajdziesz w artykule Optymalizacja dla autorów bibliotek.

Aplikacje na Androida

Aby sprawdzić, które reguły zachowywania wtyczka generuje dla modułu aplikacji, uruchom zadanie wyodrębniania dla wariantu kompilacji:

./gradlew :app:releaseExtractKeepRules

Wtyczka zapisuje reguły dla każdego wariantu w pliku app/build/generated/<variant>/androidx.annotation.keep.rules.pro. Komentarz # context: przed każdą regułą identyfikuje kod z adnotacją, który ją wygenerował.

Aby sprawdzić, jak wygenerowane reguły wpływają na optymalizację wraz z innymi regułami zachowywania, użyj analizatora konfiguracji R8.

Biblioteki Androida

Moduły biblioteki Androida nie wymagają dodatkowej konfiguracji. Gdy tworzysz plik AAR, wtyczka dodaje wygenerowane reguły do pliku proguard.txt pliku AAR po wszystkich regułach zachowywania dla konsumentów, które już zawiera plik AAR. Wtyczka rejestruje zadanie <variant>KeepRulesTransformAar dla każdego wariantu kompilacji.

Aby sprawdzić reguły, utwórz plik AAR, np. za pomocą ./gradlew :mylibrary:assembleRelease, i otwórz plik proguard.txt w wygenerowanym pliku AAR. Wygenerowane reguły pojawiają się w sekcji z tym nagłówkiem:

#################################################################
#   Rules generated from androidx.annotation.keep annotations   #
#################################################################

W systemach macOS i Linux możesz też wydrukować reguły z wiersza poleceń:

unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt

Biblioteki Kotlin Multiplatform

Moduły biblioteki Kotlin Multiplatform z celem Android też nie wymagają dodatkowej konfiguracji. Gdy tworzysz plik AAR dla celu Android, wtyczka dodaje wygenerowane reguły do pliku proguard.txt pliku AAR w taki sam sposób jak w przypadku bibliotek Androida. Wtyczka rejestruje zadanie androidMainKeepRulesTransformAar.

Aby sprawdzić reguły, uruchom to zadanie i otwórz plik proguard.txt w utworzonym przez nie pliku AAR. W systemach macOS i Linux możesz wydrukować reguły z wiersza poleceń:

./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt

Biblioteki JVM

Zanim sprawdzisz reguły modułu biblioteki JVM, skonfiguruj moduł tak, aby wtyczka dodawała wygenerowane reguły do kopii jego pliku JAR. Następnie uruchom zarejestrowane zadanie transformacji i sprawdź pliki reguł w utworzonym przez nie pliku JAR. Jeśli na przykład zadanie ma nazwę keepRulesJar, możesz wydrukować reguły w systemach macOS i Linux za pomocą tych poleceń:

./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
    "META-INF/com.android.tools/*" "META-INF/proguard/*"

Konfigurowanie bibliotek JVM

W przypadku modułów biblioteki JVM, które tworzą plik JAR, wtyczka nie modyfikuje pliku JAR automatycznie. Zamiast tego użyj funkcji annotationKeep.registerJavaArchiveTransform, aby zarejestrować zadanie, które utworzy kopię pliku JAR z wygenerowanymi regułami, a następnie opublikuj ten plik JAR. Dodaj do pliku build.gradle.kts modułu biblioteki te informacje:

plugins {
    id("java-library")
    alias(libs.plugins.jetbrains.kotlin.jvm)
    alias(libs.plugins.androidx.annotation.keep)
}

val jarTask = tasks.named<Jar>("jar")

// Apps run R8 on this library's code, so the generated rules must be inside
// the JAR. The plugin handles this automatically only for Android modules.
val keepRulesJarTask = annotationKeep.registerJavaArchiveTransform(
    taskName = "keepRulesJar",
    input = jarTask.flatMap { it.archiveFile },
    fileName = jarTask.flatMap { it.archiveFileName }.map { it.replace(".jar", "-keep-rules.jar") },
)

// Consumers of this library resolve apiElements and runtimeElements, so add
// the JAR that contains the generated rules to them.
configurations.configureEach {
    if (name == "apiElements" || name == "runtimeElements") {
        outgoing.artifact(keepRulesJarTask.flatMap { it.outputJar })
    }
}

dependencies {
    implementation(libs.androidx.annotation.keep)
}

Transformacja dodaje wygenerowane reguły do lokalizacji reguł R8 w pliku JAR: META-INF/com.android.tools/r8/proguard.txt. Jeśli plik JAR zawiera już reguły w lokalizacji META-INF/com.android.tools/ lub META-INF/proguard/, transformacja dołącza wygenerowane reguły do istniejącego pliku reguł. Więcej informacji o tych lokalizacjach znajdziesz w artykule Obsługa różnych wersji R8.

Po zarejestrowaniu transformacji sprawdź reguły w pliku JAR.

Przykład: wczytywanie opcjonalnej biblioteki dodatków

Biblioteki często używają odbicia do wczytywania opcjonalnych dodatków. Na przykład pakiet SDK do analizy może zawierać bibliotekę podstawową i dodatek do obsługi filmów. Dodatek zależy od biblioteki podstawowej, więc biblioteka podstawowa nie może bezpośrednio odwoływać się do klas dodatku. Zamiast tego biblioteka podstawowa wyszukuje klasę dodatku według nazwy w czasie działania i włącza analizę filmów tylko wtedy, gdy znajdzie tę klasę.

Ponieważ biblioteka podstawowa używa klasy dodatku tylko za pomocą odbicia, R8 może usunąć klasę lub jej konstruktor z kompilacji do publikacji aplikacji, co spowoduje niepowodzenie wyszukiwania. Błąd występuje tylko w kompilacjach do publikacji aplikacji, które zawierają dodatek, więc trudno go wykryć podczas testowania.

Bez Jetpack Keep dodatek zawiera regułę zachowywania dla konsumentów w swojej klasie, jak pokazano w sekcji Odbicie do obsługi opcjonalnych zależności:

# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
    <init>();
}

Ta reguła znajduje się w innej bibliotece niż wyszukiwanie, które jej potrzebuje, więc te 2 elementy mogą się rozsynchronizować. W przypadku Jetpack Keep dodajesz adnotację do wyszukiwania w bibliotece podstawowej:

// In the core library.
interface AnalyticsPlugin {
    fun start()
}

object AnalyticsManager {
    // The annotation and the lookup share this constant so that they can't
    // refer to different classes.
    private const val VIDEO_TRACKER_CLASS =
        "com.example.analytics.video.VideoEventTracker"

    @UsesReflectionToConstruct(
        className = VIDEO_TRACKER_CLASS,
        parameterTypes = [],
    )
    fun initialize() {
        val tracker = try {
            Class.forName(VIDEO_TRACKER_CLASS)
                .getDeclaredConstructor()
                .newInstance() as AnalyticsPlugin
        } catch (e: ClassNotFoundException) {
            // Apps that don't play video leave out the add-on, so a missing
            // class isn't an error.
            return
        }
        // This is an ordinary interface call, not a reflective one, so R8
        // keeps start() without an annotation.
        tracker.start()
    }
}

Adnotacja jest zgodna z tymi wytycznymi:

  • Dodaj adnotację do kodu, który wykonuje odbicie. Adnotacja znajduje się w initialize, która wywołuje Class.forName, a nie w klasie dodatku.
  • Wybierz adnotację na podstawie działania kodu. Wyszukiwanie tworzy instancję klasy, więc kod używa @UsesReflectionToConstruct.
  • Użyj className w przypadku klasy, do której nie możesz się odwołać. Klasa dodatku nie znajduje się na ścieżce klasy kompilacji biblioteki podstawowej, więc adnotacja nie może używać classConstant.
  • Zachowaj tylko konstruktor, który wywołuje kod. Wyszukiwanie wywołuje konstruktor bez argumentów, więc parameterTypes jest pustą tablicą.

Gdy tworzysz plik AAR biblioteki podstawowej, wtyczka dodaje regułę, która zachowuje VideoEventTracker i jego konstruktor bez argumentów, o ile R8 zachowuje initialize. Aplikacje, które używają pakietu SDK, automatycznie stosują tę regułę, nawet jeśli nie używają Jetpack Keep, więc dodatek nie potrzebuje własnej reguły. W porównaniu z regułą dodatku to podejście ma te zalety:

  • Reguła znajduje się obok wyszukiwania. Biblioteka podstawowa jest właścicielem zarówno wyszukiwania, jak i reguły, więc możesz je aktualizować razem, zamiast koordynować zmiany w 2 bibliotekach.
  • R8 może nadal usuwać nieużywany kod. Jeśli aplikacja zawiera dodatek, ale nigdy nie wywołuje initialize, wygenerowana reguła nie ma zastosowania, a R8 może usunąć VideoEventTracker. Reguła dodatku zachowuje go w każdej aplikacji, która zawiera dodatek.
  • Lint wykrywa wyszukiwania bez adnotacji. Jeśli biblioteka podstawowa później doda inne wyszukiwanie refleksyjne bez adnotacji, lint je oznaczy, dzięki czemu możesz je naprawić przed opublikowaniem biblioteki.

Aby potwierdzić wynik, sprawdź, czy wygenerowana reguła pojawia się w pliku AAR's proguard.txt, jak opisano w sekcji Biblioteki Androida. Następnie przetestuj kompilację do publikacji aplikacji, która zawiera dodatek, ponieważ w tym przypadku brakująca reguła spowoduje awarię analizy filmów.

Dodatkowe materiały