Generowanie reguł zachowywania na podstawie adnotacji

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

Jetpack Keep pomaga utrzymać skuteczność optymalizacji R8 w miarę zmian w bazie kodu:

  • Reguły zachowywania znajdują się obok kodu, który ich wymaga. Gdy zmieniasz nazwę lub usuwasz kod, który używa refleksji, aktualizujesz 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 wykonuje wywołanie. Jeśli kod wywołujący jest nieużywany i R8 go usunie (często ma to miejsce w przypadku opcjonalnych funkcji biblioteki), R8 może też usunąć element docelowy, dzięki czemu reguły z nieużywanych funkcji nie będą niepotrzebnie zwiększać rozmiaru.

  • Lint znajduje dla Ciebie 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łę podobną do tej:

# 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 refleksję:

// 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
}

Podczas kompilacji wtyczka Gradle generuje regułę zachowania dla konstruktora. W odróżnieniu od 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 używać wtyczki Androida do obsługi Gradle (AGP) w wersji 8.7 lub nowszej.

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

Typ modułu Wtyczka, którą stosuje moduł Dodatkowa konfiguracja
Aplikacja na Androida com.android.application Brak
Biblioteka Androida com.android.library Brak
Biblioteka Kotlin Multiplatform z obiektem docelowym Androida com.android.kotlin.multiplatform.library Brak
Biblioteka JVM (Java lub Kotlin) java-library lub org.jetbrains.kotlin.jvm Rejestrowanie przekształcenia JAR

Konfigurowanie Jetpack Keep

Aby używać Jetpack Keep, dodaj bibliotekę i wtyczkę Gradle do każdego modułu, który używa refleksji lub JNI. Biblioteka udostępnia adnotacje i testy linta, 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 refleksji 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, np. releaseExtractKeepRules, i przekazuje wygenerowane reguły do R8 dla tego wariantu. W przypadku modułów biblioteki wtyczka pakuje wygenerowane reguły jako reguły przechowywania konsumentów w danych wyjściowych biblioteki. Moduły biblioteki JVM wymagają dodatkowej konfiguracji.

Znajdowanie odbicia za pomocą linta

Jetpack Keep obejmuje te kontrole linta:

ReflectionWithoutKeepAnnotations
Oznacza wywołania refleksyjne, takie jak Class.forName, getDeclaredConstructor, getDeclaredMethod i getDeclaredField, które nie mają pasującej adnotacji keep.
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 na podstawie adnotacji.

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

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

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

    ./gradlew :app:lintFix
    

    Lint może automatycznie dodać adnotację, gdy może określić, do której klasy i których składowych odnosi się kod.

  3. Sprawdź każdą dodaną adnotację, aby upewnić się, że odpowiada ona działaniu kodu. Sprawdź na przykład, czy typy parametrów pasują do konstruktora lub metody wywoływanej przez kod.

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

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

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

    Zastąp obiekt zastępczy klasą, którą tworzy Twój kod. Jeśli klasa jest dostępna w czasie kompilowania, 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ź, jakie reguły zachowywania generuje wtyczka na podstawie 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ą Weryfikowanie wygenerowanych reguł.

  6. Usuń z plików reguł zachowywania reguły, które zostały zastąpione przez nowe adnotacje. Reguła może też obejmować refleksję, której nie oznaczył lint, np. refleksję w innym module lub w bibliotece, dlatego przed usunięciem reguły sprawdź ją:

    • W przypadku reguły dotyczącej całego pakietu, np. -keep class com.example.** { *; }, upewnij się, że adnotacje lub inne reguły obejmują wszystkie refleksje dotyczące klas pakietu.
    • W przypadku reguły, która zachowuje określone klasy lub elementy, upewnij się, że jest ona ściśle dopasowana do jednej z wygenerowanych reguł lub że nie zależy od niej żadna inna refleksja.
  7. Przetestuj kompilację do publikacji.

Dodawanie adnotacji do kodu

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

Wybierz adnotację na podstawie działania kodu:

Adnotacja Używaj, gdy kod
@UsesReflectionToConstruct Wywołuje konstruktor przez refleksję, na przykład za pomocą getDeclaredConstructor().newInstance().
@UsesReflectionToAccessMethod Wywołuje metodę przez refleksję, na przykład za pomocą getMethod lub getDeclaredMethod.
@UsesReflectionToAccessField Odczytuje lub zapisuje pole za pomocą refleksji, na przykład za pomocą getField lub getDeclaredField.
@UnconditionallyKeep jest wywoływana spoza kodu Kotlin lub Java aplikacji, na przykład z kodu natywnego przez JNI; Używaj tej adnotacji tylko wtedy, gdy nie możesz użyć adnotacji @UsesReflection*, które generują reguły warunkowe.

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

  • Zidentyfikuj klasę docelową za pomocą classConstant lub className. Jeśli klasa jest dostępna w czasie kompilowania, 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 kompilowania, np. gdy jest to klasa w opcjonalnej zależności. Te 2 parametry wzajemnie się wykluczają.
  • Kieruj na najbardziej szczegółową klasę. Wygenerowane reguły będą nadal dopasowywać 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 zachowają 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ż jedną klasę lub element, dodaj po jednej adnotacji dla każdego celu.

Konstruowanie obiektów z odbiciem

Użyj @UsesReflectionToConstruct, gdy kod tworzy instancję klasy przez refleksję. W poniższym przykładzie moduł uruchamiający zadania tworzy instancje implementacji interfejsu StartupTask według nazwy. Adnotacja określa interfejs, więc 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 z użyciem refleksji

Użyj @UsesReflectionToAccessMethod, gdy kod wywołuje metodę przez refleksję. Ustaw methodName na nazwę metody. W tym przykładzie kod wywołuje metodę statyczną w zależności opcjonalnej. Klasa nie jest dostępna w czasie kompilowania, 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)
    }
}

Uzyskiwanie dostępu do pól za pomocą refleksji

Używaj @UsesReflectionToAccessField, gdy kod odczytuje lub zapisuje pole za pomocą refleksji. 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żywaj adnotacji @UnconditionallyKeep w przypadku kodu wywoływanego 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 klasę, która ją zawiera, 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 element wywołujący z zewnątrz 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 przechowywania 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 konsumenta 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 adnotacjami, który ją wygenerował.

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

Biblioteki Androida

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

Aby sprawdzić reguły, utwórz plik AAR, na przykład za pomocą ./gradlew :mylibrary:assembleRelease, i otwórz proguard.txt w wygenerowanym pliku AAR. Wygenerowane reguły pojawią 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 obiektem docelowym Androida nie wymagają dodatkowej konfiguracji. Gdy tworzysz AAR dla celu Androida, wtyczka dodaje wygenerowane reguły do pliku proguard.txt AAR w taki sam sposób jak w przypadku bibliotek Androida. Wtyczka rejestruje zadanie androidMainKeepRulesTransformAar, aby to zrobić.

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

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

Biblioteki JVM

Zanim zweryfikujesz reguły modułu biblioteki JVM, skonfiguruj moduł tak, aby wtyczka dodawała wygenerowane reguły do kopii pliku JAR. Następnie uruchom zarejestrowane zadanie przekształcania i sprawdź pliki reguł w utworzonym przez nie pliku JAR. Jeśli na przykład zadanie ma nazwę keepRulesJar, możesz wyświetlić reguły w systemie 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 generują plik JAR, wtyczka nie modyfikuje go automatycznie. Zamiast tego użyj funkcji annotationKeep.registerJavaArchiveTransform, aby zarejestrować zadanie, które tworzy 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)
}

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

Po zarejestrowaniu przekształcenia sprawdź reguły w pliku JAR.

Przykład: wczytywanie opcjonalnej biblioteki dodatków

Biblioteki często używają odbicia do ładowania opcjonalnych dodatków. Na przykład pakiet SDK do analityki może zawierać bibliotekę podstawową i dodatek do 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 statystyki wideo tylko wtedy, gdy znajdzie tę klasę.

Ponieważ biblioteka podstawowa używa klasy dodatku tylko przez refleksję, R8 może usunąć klasę lub jej konstruktor z kompilacji do publikacji aplikacji, co uniemożliwi wyszukiwanie. Błąd występuje tylko w kompilacjach do publikacji aplikacji, które zawierają dodatek, więc trudno go wykryć podczas testów.

Bez Jetpack Keep dodatek dostarcza regułę zachowania konsumenta dla swojej klasy, jak pokazano w sekcji Refleksja 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 adnotację dodaje się 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 adnotacje do kodu, który wykonuje refleksję. 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ę w ścieżce klasy kompilacji biblioteki podstawowej, więc adnotacja nie może używać classConstant.
  • Zachowaj tylko konstruktor wywoływany przez 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 klasę VideoEventTracker i jej konstruktor bez argumentów, gdy R8 zachowuje klasę initialize. Aplikacje, które używają pakietu SDK, automatycznie stosują tę regułę, nawet jeśli nie korzystają z 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 pozostaje obok wyszukiwania. Biblioteka podstawowa zawiera zarówno wyszukiwanie, jak i regułę, więc aktualizujesz je 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 metody initialize, wygenerowana reguła nie ma zastosowania i R8 może usunąć metodę VideoEventTracker. Reguła dodatku zachowuje go w każdej aplikacji, która go zawiera.
  • Lintowanie wykrywa nieoznaczone wyszukiwania. Jeśli biblioteka podstawowa doda później kolejne wyszukiwanie refleksyjne bez adnotacji, lint oznaczy je, aby można było je naprawić przed opublikowaniem biblioteki.

Aby potwierdzić wynik, sprawdź, czy wygenerowana reguła pojawia się w pliku proguard.txt pakietu AAR, jak opisano w bibliotekach Androida. Następnie przetestuj kompilację do publikacji aplikacji, która zawiera dodatek, ponieważ to tam brakująca reguła może zakłócić działanie statystyk wideo.

Ograniczenia

Jetpack Keep nie wykrywa jeszcze refleksji, która znajduje klasy i elementy według ich adnotacji, ani nie generuje dla niej reguł zachowywania. Ta funkcja jest planowana.

Do tego czasu używaj reguł przechowywania. Na przykład ta reguła zachowuje wszystkie elementy oznaczone adnotacją @SomeAnnotation w klasach, które zachowuje R8:

-keepclassmembers class * {
    @com.example.annotation.SomeAnnotation <methods>;
    @com.example.annotation.SomeAnnotation <fields>;
}

Jeśli Jetpack Keep nie sprawdza się w Twoim przypadku lub masz inne uwagi, zgłoś problem.

Dodatkowe materiały