Mit Jetpack Keep können Sie die Verwendung von Reflexion und Java Native Interface (JNI) mit Annotationen direkt in Ihrem Kotlin- oder Java-Quellcode deklarieren, anstatt in separaten Keep-Regeldateien. Das Jetpack Keep Gradle-Plug-in liest diese Annotationen während des Builds und generiert die Keep-Regeln, die R8 benötigt.
Jetpack Keep sorgt dafür, dass die R8-Optimierung auch bei Änderungen an der Codebasis effektiv bleibt:
Keep-Regeln bleiben neben dem Code, für den sie erforderlich sind. Wenn Sie Code umbenennen oder löschen, der Reflexion 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 reflektiven Aufrufs nur bei, wenn auch der Code beibehalten wird, der den Aufruf ausführt. Wenn der aufrufende Code nicht verwendet und von R8 entfernt wird (häufig bei optionalen Bibliotheksfunktionen der Fall), kann R8 auch das Ziel entfernen, sodass Regeln aus nicht verwendeten Funktionen nicht zu unnötigem Code führen.
Lint findet Reflexionen für Sie. Die Bibliothek enthält Lint-Prüfungen, die reflektive Aufrufe ohne Annotationen kennzeichnen und viele der Annotationen automatisch hinzufügen können.
So funktioniert Jetpack Keep
Betrachten wir eine Funktion, die eine Klasse anhand des Namens 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 Datei mit Beibehaltungsregeln 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 annotieren Sie stattdessen die Funktion, die die Reflexion 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
}
Beim Erstellen generiert das Gradle-Plug-in eine Beibehaltungsregel 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
Damit Sie Jetpack Keep verwenden können, muss in Ihrem Projekt das Android-Gradle-Plug-in (AGP) 8.7 oder höher verwendet werden.
Das Gradle-Plug-in unterstützt die folgenden Modultypen:
| Modultyp | Plug-in, das vom Modul angewendet wird | 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 jedem Modul, das Reflexion oder JNI verwendet, sowohl die Bibliothek als auch das Gradle-Plug-in hinzu. Die Bibliothek stellt die Annotationen und Lint-Prüfungen bereit und das Plug-in wandelt die Annotationen in Beibehaltungsregeln um.
Fügen Sie in der Datei
libs.versions.tomldie Bibliothek und das Plug-in hinzu. Die Bibliothek und das 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 Ihrersettings.gradle.kts-Dateigoogle()enthalten.Deklarieren Sie das Plug-in in der
build.gradle.kts-Stammdatei zusammen mit dem 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 }Wenden Sie in der Datei
build.gradle.ktsjedes Moduls, das Reflexion oder JNI verwendet, das Plug-in an 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) }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 für diese Variante an R8. 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.
Reflexion mit Flusen finden
Jetpack Keep enthält die folgenden Lint-Prüfungen:
ReflectionWithoutKeepAnnotations- Kennzeichnet reflektive Aufrufe wie
Class.forName,getDeclaredConstructor,getDeclaredMethodundgetDeclaredField, die keine übereinstimmende Keep-Annotation haben. MissingKeepAnnotationPlugin- Kennzeichnet Module, die von
androidx.annotation.keep:keepabhängen, aber das Gradle-Plug-inandroidx.annotation.keepnicht anwenden. Ohne das Plug-in werden beim Build keine Beibehaltungsregeln aus den Annotationen generiert.
So verwenden Sie Lint, um ein Android-Modul mit Anmerkungen zu versehen:
Führe Lint für das Modul aus, um reflektierende Aufrufe zu finden, die Annotationen erfordern:
./gradlew :app:lintWenden Sie die Korrekturen an, die Lint automatisch vornehmen kann:
./gradlew :app:lintFixLint kann automatisch eine Annotation hinzufügen, wenn es ermitteln kann, auf welche Klasse und Mitglieder sich der Code bezieht.
Prüfen Sie jede hinzugefügte Annotation, um sicherzustellen, dass sie mit dem übereinstimmt, was Ihr Code tut. Prüfen Sie beispielsweise, ob die Parametertypen mit dem Konstruktor oder der Methode übereinstimmen, die Ihr Code aufruft.
Beheben Sie die verbleibenden Probleme in Android Studio. Wenn Lint die Zielklasse nicht ermitteln kann, wird die Annotation mit einem
TODO-Platzhalter eingefügt, den Sie ausfüllen müssen. Sie können die Annotation auch manuell hinzufügen.Die folgende Funktion empfängt beispielsweise den Klassennamen als Parameter, sodass Lint nicht bestimmen kann, welche Klasse erstellt wird. 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
classConstantanstelle vonclassName. In Kotlin wird Ihr Code erst kompiliert, wenn SieTODO()ersetzen.Prüfen Sie, welche Beibehaltungsregeln das Plug-in aus Ihren Annotationen generiert, bevor Sie vorhandene Regeln entfernen. Führe für ein App-Modul die Extraktionsaufgabe für die Release-Variante aus:
./gradlew :app:releaseExtractKeepRulesDas 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.Entfernen Sie die Beibehaltungsregeln, die durch die neuen Annotationen ersetzt werden, aus Ihren Dateien mit Beibehaltungsregeln. Eine Regel kann auch Reflexionen abdecken, die von Lint nicht gekennzeichnet wurden, z. B. Reflexionen 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 Reflexionen 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 Reflexion davon abhängt.
- Achten Sie bei einer paketweiten Regel wie
Testen Sie Ihren Release-Build.
Code annotieren
Fügen Sie dem Code, der die Reflexion 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 Reflexion und die generierte Regel behält dieses Ziel nur bei, wenn R8 den annotierten Code beibehält.
Wählen Sie eine Annotation basierend auf der Funktion Ihres Codes aus:
| Annotation | Verwenden Sie diese Option, wenn Ihr Code |
|---|---|
@UsesReflectionToConstruct |
Ruft einen Konstruktor über Reflexion auf, z. B. mit getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
Ruft eine Methode über Reflexion auf, z. B. mit getMethod oder getDeclaredMethod. |
@UsesReflectionToAccessField |
Liest oder schreibt ein Feld über Reflexion, z. B. mit getField oder getDeclaredField. |
@UnconditionallyKeep |
Wird von außerhalb des Kotlin- oder Java-Codes Ihrer App aufgerufen, z. B. über JNI aus nativem Code. Verwenden Sie diese Annotation nur, wenn Sie die @UsesReflection*-Annotationen, die bedingte Regeln generieren, nicht verwenden können. |
Alle @UsesReflection*-Annotationen folgen diesen Konventionen:
- Identifizieren Sie die Zielklasse mit
classConstantoderclassName. Wenn die Klasse zur Kompilierzeit verfügbar ist, verwenden SieclassConstant. Der Compiler prüft die Referenz und IDE-Refaktorierungen wie das Umbenennen aktualisieren sie. Verwenden SieclassNamenur, 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. - Spezifischste Klasse auswählen: Die generierten Regeln behalten übereinstimmende Mitglieder in der von Ihnen angegebenen Klasse und in ihren Unterklassen oder Schnittstellenimplementierungen bei. Je allgemeiner die Klasse, desto mehr Code behält R8 bei.
- Geben Sie Parametertypen an, um eine einzelne Überladung beizubehalten. Wenn Sie
parameterTypesoderparameterTypeNamesnicht festlegen, werden durch die Regeln alle Konstruktoren oder alle Überladungen der Methode beibehalten. Wenn Sie nur den Konstruktor oder die Methode ohne Argumente beibehalten möchten, übergeben Sie ein leeres Array. - Wiederholen Sie die Annotation für mehrere Ziele. Wenn eine Funktion mehr als eine Klasse oder ein Mitglied reflektiert, fügen Sie für jedes Ziel eine Annotation hinzu.
Objekte mit Reflexion erstellen
Verwenden Sie @UsesReflectionToConstruct, wenn Ihr Code eine Klasse durch Reflexion instanziiert. Im folgenden Beispiel instanziiert ein Task Runner Implementierungen einer StartupTask-Schnittstelle nach Name. 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 Reflexion aufrufen
Verwenden Sie @UsesReflectionToAccessMethod, wenn Ihr Code eine Methode über Reflexion aufruft. Legen Sie für methodName 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)
}
}
Zugriff auf Felder mit Reflexion
Verwenden Sie @UsesReflectionToAccessField, wenn Ihr Code ein Feld über Reflexion liest oder schreibt. Legen Sie für fieldName 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
Verwende @UnconditionallyKeep für Code, der von außerhalb des Kotlin- oder Java-Codes deiner App aufgerufen wird, z. B. eine Methode, die nativer Code über JNI aufruft. Annotieren Sie das aufgerufene Element, 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 annotierte Methode und die zugehörige Klasse mit ihren ursprünglichen Namen bei. Andere Klassen, auf die die Signatur der Methode verweist, werden nicht beibehalten. Wenn nativer Code von diesen Klassen abhängt, kommentieren Sie sie ebenfalls oder fügen Sie Beibehaltungsregeln 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 die Apps Jetpack Keep nicht verwenden. Weitere Informationen zu Aufbewahrungsregeln für Nutzer finden Sie unter Optimierung für Bibliotheksautoren.
Android-Apps
Wenn Sie prüfen möchten, welche Beibehaltungsregeln 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 annotierten 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 Beibehaltungsregeln auf die Optimierung auswirken.
Android-Bibliotheken
Für Android-Bibliotheksmodule ist keine zusätzliche Einrichtung erforderlich. Wenn Sie das AAR erstellen, fügt das Plug-in die generierten Regeln der proguard.txt-Datei des AAR hinzu, und zwar nach allen Consumer-Keep-Regeln, die das AAR bereits enthält. Das Plug-in registriert dazu für jede Build-Variante eine <variant>KeepRulesTransformAar-Aufgabe.
Wenn Sie die Regeln prüfen möchten, erstellen Sie das AAR, z. B. mit ./gradlew
:mylibrary:assembleRelease, und öffnen Sie proguard.txt im 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
Kotlin Multiplatform-Bibliotheksmodule mit einem Android-Ziel benötigen ebenfalls keine zusätzliche Einrichtung. Wenn Sie das AAR für das Android-Ziel erstellen, fügt das Plug-in die generierten Regeln der proguard.txt-Datei des AAR auf dieselbe Weise wie für Android-Bibliotheken hinzu. Dazu registriert das Plug-in eine androidMainKeepRulesTransformAar-Aufgabe.
Führen Sie die Aufgabe aus und öffnen Sie proguard.txt im erstellten AAR, um die Regeln zu prüfen. 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 seiner 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, wird die JAR-Datei vom Plug-in nicht automatisch geändert. Verwenden Sie stattdessen die Funktion annotationKeep.registerJavaArchiveTransform, um eine Aufgabe zu registrieren, die eine Kopie der JAR-Datei mit den generierten Regeln erstellt, und veröffentlichen Sie dann diese JAR-Datei. 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 Zielspeicherort für 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 Speicherorten 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 Analyse-SDK kann beispielsweise eine Kernbibliothek und ein Video-Add-on enthalten. Das Add-on ist von der Kernbibliothek abhängig, sodass die Kernbibliothek nicht direkt auf die Klassen des Add-ons verweisen kann. Stattdessen sucht die Kernbibliothek zur Laufzeit nach dem Namen 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 Reflexion verwendet, kann es sein, dass R8 die Klasse oder ihren Konstruktor aus dem Release-Build einer App entfernt, was dazu führt, dass die Suche fehlschlägt. Der Fehler tritt nur in Release-Builds von Apps auf, die das Add-on enthalten, und ist daher beim Testen schwer zu erkennen.
Ohne Jetpack Keep liefert 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 Suchanfrage, die sie benötigt. Daher können die beiden nicht mehr synchron sein. Mit Jetpack Keep annotieren Sie stattdessen die Suche in der Kernbibliothek:
// 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()
}
}
Die Annotation folgt diesen Richtlinien:
- Annotieren Sie den Code, der die Reflexion ausführt. Die Annotation befindet sich auf
initialize, dasClass.forNameaufruft, nicht auf der Klasse des Add-ons. - Wählen Sie die Annotation basierend auf der Funktion des Codes aus. Bei der Suche wird eine Instanz der Klasse erstellt, sodass im Code
@UsesReflectionToConstructverwendet wird. - Verwenden Sie
classNamefür eine Klasse, auf die Sie nicht verweisen können. Die Klasse des Add-ons befindet sich nicht im Kompilierungs-Klassenpfad der Hauptbibliothek, daher kann die Annotation nichtclassConstantverwenden. - Behalten Sie nur den Konstruktor bei, der vom Code aufgerufen wird. Bei der Suche wird der Konstruktor ohne Argumente aufgerufen, sodass
parameterTypesein leeres Array ist.
Wenn Sie das AAR der Kernbibliothek erstellen, fügt das Plug-in eine Regel hinzu, die VideoEventTracker und den 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. Das Add-on benötigt also keine eigene Regel.
Im Vergleich zur Regel des Add-ons bietet dieser Ansatz folgende Vorteile:
- Die Regel bleibt neben der Suche. Die Kernbibliothek enthält sowohl die Suchfunktion als auch die Regel. Sie können sie also zusammen aktualisieren, anstatt eine Änderung in zwei Bibliotheken zu koordinieren.
- R8 kann weiterhin nicht verwendeten Code entfernen. Wenn eine App das Add-on enthält, aber nie
initializeaufruft, gilt die generierte Regel nicht und R8 kannVideoEventTrackerentfernen. Die Regel des Add-ons sorgt dafür, dass es in jeder App, die das Add-on enthält, beibehalten wird. - Lint erkennt nicht annotierte Suchvorgänge. Wenn die Hauptbibliothek später eine weitere reflektierende Suche ohne Annotation hinzufügt, wird sie von Lint gekennzeichnet, damit Sie sie vor der Veröffentlichung der Bibliothek beheben können.
Prüfen Sie, ob die generierte Regel in der proguard.txt-Datei der AAR-Datei enthalten ist, wie unter Android-Bibliotheken beschrieben. Testen Sie dann einen Release-Build einer App, die das Add-on enthält, da eine fehlende Regel dort die Videoanalyse beeinträchtigen würde.
Einschränkungen
Jetpack Keep erkennt noch keine Reflexion, die Klassen und Mitglieder anhand ihrer Annotationen findet, und generiert auch keine Keep-Regeln dafür. Diese Funktion ist geplant.
Bis dahin können Sie Aufbewahrungsregeln für diese Art von Reflexion verwenden. Mit der folgenden Regel werden beispielsweise alle Mitglieder, die mit @SomeAnnotation annotiert sind, in den Klassen beibehalten, die R8 beibehält:
-keepclassmembers class * {
@com.example.annotation.SomeAnnotation <methods>;
@com.example.annotation.SomeAnnotation <fields>;
}
Wenn Jetpack Keep für Ihren Anwendungsfall nicht funktioniert oder Sie anderes Feedback haben, melden Sie ein Problem.
Zusätzliche Ressourcen
- Jetpack Keep-Beispiel: Ein Android Studio-Projekt, in dem Jetpack Keep in einer App, einer Android-Bibliothek, einer Kotlin Multiplatform-Bibliothek und einer JVM-Bibliothek verwendet wird.
- Anwendungsfälle und Beispiele für Aufbewahrungsregeln: Aufbewahrungsregeln für gängige Reflexions- und JNI-Szenarien.
- Best Practices befolgen: Anleitung zum Schreiben von Aufbewahrungsregeln mit engem Geltungsbereich.