Aufbewahrungsregeln aus Anmerkungen generieren

Mit Jetpack Keep können Sie die Verwendung von Reflection und Java Native Interface (JNI) mit Annotationen direkt in Ihrem Kotlin- oder Java-Quellcode deklarieren, anstatt in separaten Keep-Regel-Dateien. Das Jetpack Keep Gradle-Plug-in liest diese Annotationen zur Build-Zeit und generiert die Keep-Regeln, die R8 benötigt.

Jetpack Keep trägt dazu bei, dass die R8-Optimierung auch bei Änderungen an der Codebasis effektiv bleibt:

  • Keep-Regeln bleiben neben dem Code, für den sie benötigt werden. Wenn Sie Code umbenennen oder löschen, der Reflection verwendet, aktualisieren Sie die Annotation an derselben Stelle. Es gibt keine separate Regeldatei, die synchronisiert werden muss.

  • Generierte Regeln sind bedingt. R8 behält das Ziel eines reflektierenden Aufrufs nur bei, wenn auch der Code beibehalten wird, der den Aufruf ausführt. Wenn der aufrufende Code nicht verwendet wird und R8 ihn entfernt (was bei optionalen Bibliotheksfunktionen häufig der Fall ist), kann R8 auch das Ziel entfernen, sodass Regeln aus nicht verwendeten Funktionen nicht zu unnötigem Code führen.

  • Lint findet Reflection für Sie. Die Bibliothek enthält Lint-Prüfungen, die reflektierende Aufrufe kennzeichnen, die nicht mit Annotationen versehen sind, und viele der Annotationen automatisch hinzufügen können.

Funktionsweise von Jetpack Keep

Nehmen wir an, Sie haben eine Funktion, die eine Klasse nach Namen instanziiert. Wenn R8 nicht erkennen kann, dass Ihr Code die Klasse verwendet, wird der Konstruktor der Klasse entfernt und die App stürzt zur Laufzeit ab. Um den Absturz zu verhindern, können Sie Ihrer Keep-Regel-Datei eine Regel wie die folgende hinzufügen:

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

Mit Jetpack Keep versehen Sie stattdessen die Funktion mit einer Annotation, die die Reflection ausführt:

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

Zur Build-Zeit generiert das Gradle-Plug-in eine Keep-Regel für den Konstruktor. Im Gegensatz zur vorherigen manuell geschriebenen Regel ist die generierte Regel bedingt: R8 behält den Konstruktor nur bei, wenn loadResourceLocator in der optimierten App verbleibt.

Voraussetzungen

Für die Verwendung von Jetpack Keep ist Android-Gradle-Plug-in (AGP) 8.7 oder höher erforderlich.

Das Gradle-Plug-in unterstützt die folgenden Modultypen:

Modultyp Plug-in, das das Modul anwendet Zusätzliche Einrichtung
Android-App com.android.application Keine
Android-Bibliothek com.android.library Keine
Kotlin Multiplatform-Bibliothek mit einem Android-Ziel com.android.kotlin.multiplatform.library Keine
JVM-Bibliothek (Java oder Kotlin) java-library oder org.jetbrains.kotlin.jvm JAR-Transformation registrieren

Jetpack Keep einrichten

Wenn Sie Jetpack Keep verwenden möchten, fügen Sie sowohl die Bibliothek als auch das Gradle-Plug-in jedem Modul hinzu, das Reflection oder JNI verwendet. Die Bibliothek enthält die Annotationen und Lint-Prüfungen und das Plug-in wandelt die Annotationen in Keep-Regeln um.

  1. Fügen Sie in der Datei libs.versions.toml die Bibliothek und das Plug-in hinzu. Bibliothek und Plug-in haben dieselbe Version:

    [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 lädt das Plug-in aus dem Maven-Repository von Google herunter. Achten Sie daher darauf, dass die pluginManagement-Repositories in der Datei settings.gradle.kts google() enthalten.

  2. Deklarieren Sie das Plug-in in der build.gradle.kts-Datei des Stammverzeichnisses neben AGP, ohne es anzuwenden:

    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. Wenden Sie das Plug-in in der build.gradle.kts-Datei jedes Moduls an, das Reflection oder JNI verwendet, und fügen Sie die Bibliotheksabhängigkeit hinzu:

    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. Synchronisieren Sie Ihr Projekt mit Gradle-Dateien.

Für jede Build-Variante eines Android-App-Moduls registriert das Plug-in eine <variant>ExtractKeepRules Aufgabe, z. B. releaseExtractKeepRules, und übergibt die generierten Regeln an R8 für diese Variante. Bei Bibliotheksmodulen verpackt das Plug-in die generierten Regeln als Consumer-Keep-Regeln in der Ausgabe der Bibliothek. Für JVM-Bibliotheksmodule ist eine zusätzliche Einrichtung erforderlich.

Reflection mit Lint finden

Jetpack Keep enthält die folgenden Lint-Prüfungen:

ReflectionWithoutKeepAnnotations
Kennzeichnet reflektierende Aufrufe wie Class.forName, getDeclaredConstructor, getDeclaredMethod und getDeclaredField, die keine passende Keep-Annotation haben.
MissingKeepAnnotationPlugin
Kennzeichnet Module, die von androidx.annotation.keep:keep abhängen, aber das Gradle-Plug-in androidx.annotation.keep nicht anwenden. Ohne das Plug-in werden beim Build keine Keep-Regeln aus den Annotationen generiert.

So verwenden Sie Lint, um ein Android-Modul mit Annotationen zu versehen:

  1. Führen Sie Lint für das Modul aus, um reflektierende Aufrufe zu finden, die Annotationen benötigen:

    ./gradlew :app:lint
    
  2. Wenden Sie die Korrekturen an, die Lint automatisch vornehmen kann:

    ./gradlew :app:lintFix
    

    Lint kann automatisch eine Annotation hinzufügen, wenn es ermitteln kann, auf welche Klasse und Mitglieder sich der Code bezieht.

  3. Prüfen Sie jede hinzugefügte Annotation, um zu bestätigen, dass sie dem entspricht, was Ihr Code tut. Prüfen Sie beispielsweise, ob die Parametertypen mit dem Konstruktor oder der Methode übereinstimmen, die Ihr Code aufruft.

  4. Beheben Sie die verbleibenden Ergebnisse in Android Studio. Wenn Lint die Zielklasse nicht ermitteln kann, fügt die Schnellkorrektur die Annotation mit einem TODO-Platzhalter ein, den Sie vervollständigen müssen. Sie können die Annotation auch manuell hinzufügen.

    Die folgende Funktion erhält beispielsweise den Klassennamen als Parameter, sodass Lint nicht ermitteln kann, welche Klasse sie erstellt. Die Schnellkorrektur fügt eine Annotation mit einem Platzhalter hinzu:

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

    Ersetzen Sie den Platzhalter durch die Klasse, die Ihr Code erstellt. Wenn die Klasse zur Kompilierzeit verfügbar ist, verwenden Sie classConstant anstelle von className. In Kotlin wird Ihr Code erst kompiliert, wenn Sie TODO() ersetzen.

  5. Prüfen Sie, welche Keep-Regeln das Plug-in aus Ihren Annotationen generiert, bevor Sie vorhandene Regeln entfernen. Führen Sie für ein App-Modul die Extraktionsaufgabe für die Release-Variante aus:

    ./gradlew :app:releaseExtractKeepRules
    

    Das Plug-in schreibt die Regeln in app/build/generated/release/androidx.annotation.keep.rules.pro. Informationen zu Bibliotheksmodulen finden Sie unter Generierte Regeln prüfen.

  6. Entfernen Sie die Keep-Regeln, die durch die neuen Annotationen ersetzt werden, aus Ihren Keep-Regel-Dateien. Eine Regel kann auch Reflection abdecken, die von Lint nicht gekennzeichnet wurde, z. B. Reflection in einem anderen Modul oder in einer Bibliothek. Prüfen Sie daher jede Regel, bevor Sie sie entfernen:

    • Achten Sie bei einer paketweiten Regel wie -keep class com.example.** { *; } darauf, dass Annotationen oder andere Regeln alle Reflection für die Klassen des Pakets abdecken.
    • Achten Sie bei einer Regel, die bestimmte Klassen oder Mitglieder beibehält, darauf, dass sie genau mit einer der generierten Regeln übereinstimmt oder dass keine andere Reflection davon abhängt.
  7. Testen Sie Ihren Release-Build.

Code mit Annotationen versehen

Fügen Sie dem Code, der die Reflection ausführt, eine Jetpack Keep-Annotation hinzu: die Funktion, den Konstruktor, das Feld oder die Klasse, die den reflektierenden Aufruf enthält. Die Annotation beschreibt das Ziel der Reflection und die generierte Regel behält dieses Ziel nur bei, wenn R8 den mit Annotationen versehenen Code beibehält.

Wählen Sie eine Annotation basierend auf der Funktion Ihres Codes aus:

Annotation Verwendung
@UsesReflectionToConstruct Ruft einen Konstruktor über Reflection auf, z. B. mit getDeclaredConstructor().newInstance().
@UsesReflectionToAccessMethod Ruft eine Methode über Reflection auf, z. B. mit getMethod oder getDeclaredMethod.
@UsesReflectionToAccessField Liest oder schreibt ein Feld über Reflection, z. B. mit getField oder getDeclaredField.
@UnconditionallyKeep Wird von außerhalb des Kotlin- oder Java-Codes Ihrer App aufgerufen, z. B. von nativem Code über JNI. Verwenden Sie diese Annotation nur, wenn Sie die @UsesReflection* Annotationen nicht verwenden können, die bedingte Regeln generieren.

Für alle @UsesReflection*-Annotationen gelten die folgenden Konventionen:

  • Zielklasse mit classConstant oder className identifizieren. Wenn die Klasse zur Kompilierzeit verfügbar ist, verwenden Sie classConstant. Der Compiler prüft den Verweis und IDE-Refactorings wie das Umbenennen aktualisieren ihn. Verwenden Sie className nur, wenn die Klasse zur Kompilierzeit nicht verfügbar ist, z. B. eine Klasse in einer optionalen Abhängigkeit. Diese beiden Parameter schließen sich gegenseitig aus.
  • Die spezifischste Klasse als Ziel verwenden Die generierten Regeln behalten übereinstimmende Mitglieder in der von Ihnen angegebenen Klasse und in ihren Unterklassen oder Schnittstellenimplementierungen bei. Je allgemeiner die Klasse ist, desto mehr Code behält R8 bei.
  • Parametertypen angeben, um eine einzelne Überladung beizubehalten Wenn Sie parameterTypes oder parameterTypeNames nicht festlegen, behalten die Regeln alle Konstruktoren oder alle Überladungen der Methode bei. Wenn Sie nur den Konstruktor oder die Methode ohne Argumente beibehalten möchten, übergeben Sie ein leeres Array.
  • Annotation für mehrere Ziele wiederholen Wenn eine Funktion mehr als eine Klasse oder ein Mitglied reflektiert, fügen Sie für jedes Ziel eine Annotation hinzu.

Objekte mit Reflection erstellen

Verwenden Sie @UsesReflectionToConstruct, wenn Ihr Code eine Klasse über Reflection instanziiert. Im folgenden Beispiel instanziiert ein Task-Runner Implementierungen einer StartupTask-Schnittstelle nach Namen. Da die Annotation die Schnittstelle angibt, behalten die generierten Regeln den Konstruktor ohne Argumente jeder StartupTask-Implementierung bei, solange R8 execute beibehält:

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()
    }
}

Methoden mit Reflection aufrufen

Verwenden Sie @UsesReflectionToAccessMethod, wenn Ihr Code eine Methode über Reflection aufruft. Legen Sie methodName auf den Namen der Methode fest. Im folgenden Beispiel ruft der Code eine statische Methode in einer optionalen Abhängigkeit auf. Die Klasse ist zur Kompilierzeit nicht verfügbar, daher wird in der Annotation className anstelle von classConstant verwendet:

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

Auf Felder mit Reflection zugreifen

Verwenden Sie @UsesReflectionToAccessField, wenn Ihr Code ein Feld über Reflection liest oder schreibt. Legen Sie fieldName auf den Namen des Felds fest:

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

Code beibehalten, der von außerhalb Ihrer App aufgerufen wird

Verwenden Sie @UnconditionallyKeep für Code, der von außerhalb des Kotlin- oder Java-Codes Ihrer App aufgerufen wird, z. B. eine Methode, die von nativem Code über JNI aufgerufen wird. Versehen Sie das aufgerufene Element mit einer Annotation, nicht den Code, der es aufruft:

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()
}

Die generierte Regel behält die mit Annotationen versehene Methode und ihre enthaltende Klasse mit ihren ursprünglichen Namen bei. Andere Klassen, auf die in der Signatur der Methode verwiesen wird, werden nicht beibehalten. Wenn nativer Code von diesen Klassen abhängt, versehen Sie sie ebenfalls mit Annotationen oder fügen Sie Keep-Regeln für sie hinzu. Weitere Informationen zu JNI und R8 finden Sie unter Interaktion mit nativem Code über JNI.

Standardmäßig behält @UnconditionallyKeep den Namen des Elements bei. Wenn der externe Aufrufer nicht vom Namen abhängt, legen Sie shouldPreserveName = false fest, damit R8 das Element umbenennen kann.

Generierte Regeln prüfen

In diesem Abschnitt wird erläutert, wie Sie die generierten Regeln für jeden Modultyp prüfen. Bei einem App-Modul übergibt das Plug-in die Regeln direkt an R8. Bei einem Bibliotheksmodul verpackt das Plug-in die Regeln als Consumer-Keep-Regeln in der Ausgabe der Bibliothek. Apps, die von Ihrer Bibliothek abhängen, wenden diese Regeln automatisch an, wenn sie R8 ausführen, auch wenn sie Jetpack Keep nicht verwenden. Weitere Informationen zu Consumer-Keep-Regeln finden Sie unter Optimierung für Bibliotheksautoren.

Android-Apps

Wenn Sie prüfen möchten, welche Keep-Regeln das Plug-in für ein App-Modul generiert, führen Sie die Extraktionsaufgabe für eine Build-Variante aus:

./gradlew :app:releaseExtractKeepRules

Das Plug-in schreibt die Regeln für jede Variante in app/build/generated/<variant>/androidx.annotation.keep.rules.pro. Ein # context:-Kommentar vor jeder Regel gibt den mit Annotationen versehenen Code an, der sie erzeugt hat.

Mit dem R8 Configuration Analyzer können Sie prüfen, wie sich die generierten Regeln zusammen mit Ihren anderen Keep-Regeln auf die Optimierung auswirken.

Android-Bibliotheken

Für Android-Bibliotheksmodule ist keine zusätzliche Einrichtung erforderlich. Wenn Sie die AAR erstellen, fügt das Plug-in die generierten Regeln der Datei proguard.txt der AAR hinzu, nach allen Consumer-Keep-Regeln, die die AAR bereits enthält. Das Plug-in registriert für jede Build-Variante eine <variant>KeepRulesTransformAar-Aufgabe, um dies zu tun.

Wenn Sie die Regeln prüfen möchten, erstellen Sie die AAR, z. B. mit ./gradlew :mylibrary:assembleRelease, und öffnen Sie proguard.txt in der generierten AAR. Die generierten Regeln werden in einem Abschnitt mit der folgenden Überschrift angezeigt:

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

Unter macOS und Linux können Sie die Regeln auch über die Befehlszeile ausgeben:

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

Kotlin Multiplatform-Bibliotheken

Für Kotlin Multiplatform-Bibliotheksmodule mit einem Android-Ziel ist ebenfalls keine zusätzliche Einrichtung erforderlich. Wenn Sie die AAR für das Android-Ziel erstellen, fügt das Plug-in die generierten Regeln auf dieselbe Weise wie bei Android-Bibliotheken der Datei proguard.txt der AAR hinzu. Das Plug-in registriert dafür eine androidMainKeepRulesTransformAar-Aufgabe.

Wenn Sie die Regeln prüfen möchten, führen Sie diese Aufgabe aus und öffnen Sie proguard.txt in der AAR, die dadurch erstellt wird. Unter macOS und Linux können Sie die Regeln über die Befehlszeile ausgeben:

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

JVM-Bibliotheken

Bevor Sie die Regeln für ein JVM-Bibliotheksmodul prüfen können, müssen Sie das Modul so konfigurieren, dass das Plug-in die generierten Regeln einer Kopie der JAR-Datei hinzufügt. Führen Sie dann die registrierte Transformationsaufgabe aus und prüfen Sie die Regeldateien in der erstellten JAR-Datei. Wenn Sie die Aufgabe beispielsweise keepRulesJar genannt haben, können Sie die Regeln unter macOS und Linux mit den folgenden Befehlen ausgeben:

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

JVM-Bibliotheken konfigurieren

Bei JVM-Bibliotheksmodulen, die eine JAR-Datei erzeugen, ändert das Plug-in die JAR-Datei nicht automatisch. Verwenden Sie stattdessen die Funktion annotationKeep.registerJavaArchiveTransform, um eine Aufgabe zu registrieren, die eine Kopie der JAR-Datei mit den generierten Regeln erstellt und diese JAR-Datei dann veröffentlicht. Fügen Sie der Datei build.gradle.kts des Bibliotheksmoduls Folgendes hinzu:

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

Die Transformation fügt die generierten Regeln dem Zielort der R8-Regeln der JAR-Datei hinzu: META-INF/com.android.tools/r8/proguard.txt. Wenn die JAR-Datei bereits Regeln in META-INF/com.android.tools/ oder META-INF/proguard/ enthält, werden die generierten Regeln stattdessen an die vorhandene Regeldatei angehängt. Weitere Informationen zu diesen Orten finden Sie unter Verschiedene R8-Versionen unterstützen.

Nachdem Sie die Transformation registriert haben, prüfen Sie die Regeln in der JAR-Datei.

Beispiel: Optionale Add-on-Bibliothek laden

Bibliotheken verwenden häufig Reflection, um optionale Add-ons zu laden. Ein Analytics SDK kann beispielsweise eine Kernbibliothek und ein Video-Add-on enthalten. Das Add-on hängt von der Kernbibliothek ab, daher kann die Kernbibliothek nicht direkt auf die Klassen des Add-ons verweisen. Stattdessen sucht die Kernbibliothek zur Laufzeit nach der Klasse des Add-ons und aktiviert die Videoanalyse nur, wenn sie die Klasse findet.

Da die Kernbibliothek die Klasse des Add-ons nur über Reflection verwendet, kann R8 die Klasse oder ihren Konstruktor aus dem Release-Build einer App entfernen, wodurch die Suche fehlschlägt. Der Fehler tritt nur in Release-Builds von Apps auf, die das Add-on enthalten, daher ist er beim Testen schwer zu finden.

Ohne Jetpack Keep enthält das Add-on eine Consumer-Keep-Regel für seine Klasse, wie unter Reflection zur Unterstützung optionaler Abhängigkeiten gezeigt:

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

Diese Regel befindet sich in einer anderen Bibliothek als die Suche, die sie benötigt, sodass die beiden nicht synchronisiert werden können. Mit Jetpack Keep versehen Sie stattdessen die Suche in der Kernbibliothek mit einer Annotation:

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

Für die Annotation gelten die folgenden Richtlinien:

  • Den Code mit Annotationen versehen, der die Reflection ausführt Die Annotation befindet sich in initialize, das Class.forName aufruft, nicht in der Klasse des Add-ons.
  • Annotation basierend auf der Funktion des Codes auswählen Die Suche erstellt eine Instanz der Klasse, daher verwendet der Code @UsesReflectionToConstruct.
  • className`className` für eine Klasse verwenden, auf die Sie nicht verweisen können Die Klasse des Add-ons befindet sich nicht im Kompilier-Classpath der Kernbibliothek, daher kann die Annotation classConstant nicht verwenden.
  • Nur den Konstruktor beibehalten, den der Code aufruft Die Suche ruft den Konstruktor ohne Argumente auf, daher ist parameterTypes ein leeres Array.

Wenn Sie die AAR der Kernbibliothek erstellen, fügt das Plug-in eine Regel hinzu, die VideoEventTracker und seinen Konstruktor ohne Argumente beibehält, wenn R8 initialize beibehält. Apps, die das SDK verwenden, wenden diese Regel automatisch an, auch wenn sie Jetpack Keep nicht verwenden, sodass das Add-on keine eigene Regel benötigt. Im Vergleich zur Regel des Add-ons bietet dieser Ansatz folgende Vorteile:

  • Die Regel bleibt neben der Suche. Die Kernbibliothek enthält sowohl die Suche als auch die Regel, sodass Sie sie zusammen aktualisieren können, anstatt eine Änderung in zwei Bibliotheken zu koordinieren.
  • R8 kann weiterhin nicht verwendeten Code entfernen. Wenn eine App das Add-on enthält, aber initialize nie aufruft, wird die generierte Regel nicht angewendet und R8 kann VideoEventTracker entfernen. Die Regel des Add-ons behält es in jeder App bei, die das Add-on enthält.
  • Lint findet nicht mit Annotationen versehene Suchen. Wenn die Kernbibliothek später eine weitere reflektierende Suche ohne Annotation hinzufügt, wird sie von Lint gekennzeichnet, sodass Sie sie beheben können, bevor Sie die Bibliothek veröffentlichen.

Prüfen Sie, ob die generierte Regel in der Datei der AAR's proguard.txt angezeigt wird, wie unter Android-Bibliotheken beschrieben. Testen Sie dann einen Release-Build einer App, die das Add-on enthält, da dort eine fehlende Regel die Videoanalyse unterbrechen würde.

Zusätzliche Ressourcen