Als Bibliotheksautor müssen Sie dafür sorgen, dass App-Entwickler Ihre Bibliothek problemlos in ihre App einbinden können und gleichzeitig eine hohe Qualität für Endnutzer gewährleistet ist. Das bedeutet, dass Ihre Bibliothek mit der Android-Optimierung (R8) kompatibel sein muss, ohne dass der Entwickler zusätzliche Einstellungen vornehmen muss. Alternativ müssen Sie dokumentieren, dass die Bibliothek möglicherweise nicht für die Verwendung auf Android geeignet ist. Es ist entscheidend, dass Bibliotheken, die für die Verwendung auf Android vorgesehen sind, wichtige App Optimierungen nicht verhindern und zusätzliche Optimierungsanforderungen erfüllen.
Diese Dokumentation richtet sich an Entwickler von veröffentlichten Bibliotheken, kann aber auch für Entwickler von internen Bibliotheksmodulen in einer großen, modularisierten App nützlich sein.
Wenn Sie App-Entwickler sind und mehr über die Optimierung Ihrer Android-App erfahren möchten, lesen Sie den Hilfeartikel App-Optimierung aktivieren. Informationen dazu, welche Bibliotheken geeignet sind finden Sie unter Bibliotheken sorgfältig auswählen.
Arten von Keep-Regeln
Es gibt zwei verschiedene Arten von Keep-Regeln, die in Bibliotheken verwendet werden können:
- Consumer-Keep-Regeln müssen Regeln angeben, die alles beibehalten, was die Bibliothek widerspiegelt. Wenn eine Bibliothek Reflection oder JNI verwendet, um ihren Code oder Code aufzurufen, der von einer Client-App definiert wurde, müssen diese Regeln beschreiben, welcher Code beibehalten werden muss. Bibliotheken sollten Consumer-Keep-Regeln enthalten, die dasselbe Format wie App-Keep-Regeln verwenden. Diese Regeln werden in Bibliotheksartefakte (AARs oder JARs) gebündelt und bei der Android-App-Optimierung automatisch verwendet, wenn die Bibliothek verwendet wird. Diese Regeln werden in der
Datei verwaltet, die mit der
consumerProguardFilesProperty in Ihrerbuild.gradle.kts(oderbuild.gradle) Datei angegeben ist. Weitere Informationen finden Sie unter Consumer-Keep-Regeln schreiben. - Keep-Regeln für den Bibliotheks-Build werden angewendet, wenn Ihre Bibliothek erstellt wird. Sie sind nur erforderlich, wenn Sie Ihre Bibliothek zur Build-Zeit teilweise optimieren möchten. Sie müssen verhindern, dass die öffentliche API der Bibliothek entfernt wird. Andernfalls ist die öffentliche API nicht in der Bibliotheksverteilung enthalten und App-Entwickler können die Bibliothek nicht verwenden. Diese Regeln werden in der Datei
verwaltet, die mit der
proguardFilesProperty in der Dateibuild.gradle.kts(oderbuild.gradle) angegeben ist. Weitere Informationen finden Sie unter AAR-Bibliotheks-Build optimieren.
Optimierungsanforderungen und -richtlinien
Die R8-Konfiguration in Bibliotheken hat globale Auswirkungen auf die endgültige Binärgröße und Leistung der verwendeten App. Neben den allgemeinen Best Practices für Keep-Regelnmüssen Bibliotheksautoren bestimmte Anforderungen erfüllen und zusätzliche Richtlinien berücksichtigen.
Optimierungsanforderungen erfüllen
Ineffizienzen in Bibliotheken tragen maßgeblich zu App-Bloat, verschwendetem Arbeitsspeicher, langsamen Starts und ANRs (Application Not Responding-Fehlern) bei. Bibliotheken müssen die folgenden Anforderungen erfüllen, um die App-Qualität und die Nutzererfahrung nicht erheblich zu beeinträchtigen.
Keine umfassenden oder paketweiten Keep-Regeln:Ihre Bibliothek darf keine umfassenden Keep-Regeln enthalten, die den Großteil des Codes in Ihrer Bibliothek oder in einer anderen Bibliothek beibehalten. Umfassende Keep-Regeln können Abstürze kurzfristig beheben, aber sie erhöhen die App-Größe aller Apps, die Ihre Bibliothek verwenden.
Verwenden Sie keine paketweiten Keep-Regeln (z. B.
-keep class com.mylibrary.** {*; }) für Pakete in Ihrer Bibliothek oder anderen referenzierten Bibliotheken. Solche Regeln beschränken die Optimierung für diese Pakete in allen Apps, die Ihre Bibliothek verwenden.Keine unangemessenen globalen Regeln: Verwenden Sie niemals globale Optionen wie
-dontobfuscateoder-allowaccessmodification.Nach Möglichkeit Codegenerierung anstelle von Reflection verwenden:Verwenden Sie nach Möglichkeit Codegenerierung (codegen) anstelle von Reflection. Codegenerierung und Reflection sind beides gängige Ansätze, um Boilerplate-Code bei der Programmierung zu vermeiden. Codegenerierung ist jedoch besser mit einem App-Optimizer wie R8 kompatibel.
Bei der Codegenerierung wird der Code während des Build-Prozesses analysiert und geändert. Da nach der Kompilierung keine größeren Änderungen mehr vorgenommen werden, weiß der Optimizer, welcher Code letztendlich benötigt wird und welcher sicher entfernt werden kann.
Bei der Reflection wird der Code zur Laufzeit analysiert und manipuliert. Da der Code erst bei der Ausführung wirklich fertig ist, weiß der Optimizer nicht, welcher Code sicher entfernt werden kann. Wahrscheinlich wird Code entfernt, der zur Laufzeit dynamisch über Reflection verwendet wird, was zu App-Abstürzen bei Nutzern führt.
Viele moderne Bibliotheken verwenden Codegenerierung anstelle von Reflection. Siehe KSP für einen gängigen Einstiegspunkt, der von Room, Hilt und vielen anderen verwendet wird.
Vollständigen R8-Modus unterstützen: Ihre Bibliothek sollte nicht abstürzen, wenn der vollständige R8-Modus aktiviert ist. Der vollständige Modus von R8 ist der empfohlene Modus für die Verwendung von R8 und ist seit AGP 8.0 die Standardeinstellung, die 2023 stabilisiert wurde. Wenn Ihre Bibliothek unter R8 abstürzt, besteht die Lösung darin, den spezifischen Reflection- oder JNI-Einstiegspunkt zu identifizieren und eine gezielte Regel hinzuzufügen, anstatt das gesamte Paket beizubehalten.
Weitere Empfehlungen
Neben den Optimierungsanforderungen gibt es noch weitere Empfehlungen.
- Verwenden Sie
-repackageclassesnicht in der Datei mit den Consumer-Keep-Regeln Ihrer Bibliothek. Um den Build Ihrer Bibliothek zu optimieren, können Sie-repackageclassesmit einem internen Paketnamen wie<your.library.package>.internalin der Datei mit den Keep-Regeln für den Build Ihrer Bibliothek verwenden. Dadurch kann die Effizienz Ihrer Bibliothek in nicht optimierten Apps verbessert werden. Im Allgemeinen ist dies jedoch nicht erforderlich, da Apps ebenfalls optimiert werden sollten. - Deklarieren Sie alle Attribute, die für die Funktion Ihrer Bibliothek erforderlich sind, in den Dateien mit den Keep-Regeln Ihrer Bibliothek, auch wenn es eine Überschneidung mit den in
proguard-android-optimize.txtdefinierten Attributen gibt. - Wenn Sie die folgenden Attribute in der Verteilung Ihrer Bibliothek benötigen, verwalten Sie sie in der Datei mit den Keep-Regeln für den Build Ihrer Bibliothek und nicht in der Datei mit den Consumer-Keep-Regeln Ihrer Bibliothek:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Bibliotheksautoren sollten das Attribut
RuntimeVisibleAnnotationsin ihren Consumer-Keep-Regeln beibehalten, wenn Annotationen zur Laufzeit verwendet werden. - Bibliotheksautoren sollten die folgenden globalen Optionen nicht in ihren Consumer-Keep-Regeln verwenden:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Wann Reflection in Ordnung ist
Wenn Sie Reflection verwenden müssen, sollten Sie sie nur für eine der folgenden Optionen verwenden:
- Bestimmte Targeting-Typen (bestimmte Schnittstellen-Implementierer oder Unterklassen)
- Code mit einer bestimmten Laufzeitannotation
Durch die Verwendung von Reflection auf diese Weise werden die Laufzeitkosten begrenzt und gezielte Consumer-Keep-Regeln können geschrieben werden.
Diese spezifische und gezielte Form der Reflection ist ein Muster, das sowohl im Android-Framework (z. B. beim Erstellen von Aktivitäten, Ansichten und Drawables) als auch in AndroidX-Bibliotheken (z. B. beim Erstellen von WorkManager
ListenableWorkers oder RoomDatabases) zu finden ist. Im Gegensatz dazu ist die offene Reflection von Gson nicht für die Verwendung in Android-Apps geeignet.
Häufige Missverständnisse
Einige häufige Missverständnisse können dazu führen, dass Sie R8 falsch konfigurieren. Dazu gehören:
Falsches Verständnis der Optimierungen von R8: Anders als allgemein angenommen beschränken sich die Optimierungen von R8 nicht nur auf die Verschleierung, sondern umfassen auch die Codekomprimierung und logische Optimierungen mit Methoden-Inlining und Klassen-Zusammenführungstechniken. Weitere Informationen finden Sie unter R8-Optimierung Übersicht.
Optimierung von verschleierten Bibliotheken umgehen: Ein häufiger Fehler besteht darin, eine Bibliothek von der Optimierung auszuschließen, weil sie beim Kompilieren in eine AAR-Datei (Android-Archiv) oder JAR-Datei (Java-Archiv) optimiert oder verschleiert wurde. Die Optimierungen während des Bibliotheks-Builds sind begrenzt und Ihre App sollte die Optimierung der Bibliothek nicht deaktivieren, indem sie in eine Keep-Regel aufgenommen wird. Weitere Informationen finden Sie unter AAR-Bibliotheks-Build optimieren.
Falsches Verständnis der Option
-keep: Die Regel-keepverhindert, dass R8 seine Optimierungsdurchläufe ausführt. Weitere Informationen finden Sie unter Die richtige Keep-Option auswählen.
Qualität von Consumer-Keep-Regeln validieren
Wenn Sie prüfen möchten, ob Ihre Bibliothek zu viel Codeoptimierung verhindert, wenn sie in eine App eingebettet ist, verwenden Sie eine Bibliotheksbeispiel- oder Integrationstest-App. Aktivieren Sie R8 und prüfen Sie die allgemeine Qualität der R8-Konfiguration der App mit dem R8 Configuration Analyzer.
Wenn Ihre Consumer-Regeln die Optimierung für große Teile des Codes Ihrer Bibliothek einschränken, müssen sie verfeinert werden. Beschränken Sie die Consumer-Regeln und testen Sie alle betroffenen Aspekte Ihrer Bibliothek in einer App, in der R8 aktiviert ist.
Regelverpackung konfigurieren
Damit Ihre Consumer-Keep-Regeln korrekt angewendet werden, müssen Sie sie je nach Bibliotheksformat entsprechend verpacken.
Wenn Sie Jetpack Keep verwenden, um die Reflection- und JNI Nutzung Ihrer Bibliothek zu deklarieren, kann das Gradle-Plug-in die generierten Regeln als Consumer-Keep-Regeln für Sie verpacken.
AAR-Bibliotheken
Verwenden Sie die Option consumerProguardFiles im Build-Skript des Android-Bibliotheksmoduls, um Consumer-Regeln für eine AAR-Bibliothek hinzuzufügen. Weitere Informationen finden Sie in unserem
Leitfaden zum Erstellen von Bibliotheksmodulen.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
JAR-Bibliotheken
Wenn Sie Regeln mit Ihrer Kotlin- oder Java-Bibliothek bündeln möchten, die als JAR ausgeliefert wird, platzieren Sie die Regeldatei mit einem beliebigen Dateinamen im Verzeichnis META-INF/proguard/ der endgültigen JAR-Datei.
Wenn sich Ihr Code beispielsweise in <libraryroot>/src/main/kotlin befindet, platzieren Sie eine Consumer
Regeldatei unter
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
. Die Regeln werden dann an der richtigen Stelle in Ihrer Ausgabe-JAR-Datei gebündelt.
Prüfen Sie, ob die Regeln in der endgültigen JAR-Datei korrekt gebündelt sind, indem Sie prüfen, ob sie sich im Verzeichnis META-INF/proguard befinden.
AAR-Bibliotheks-Build optimieren (erweitert)
Im Allgemeinen müssen Sie einen Bibliotheks-Build nicht direkt optimieren, da die möglichen Optimierungen zur Build-Zeit der Bibliothek sehr begrenzt sind. Als Bibliotheksentwickler müssen Sie mehrere Optimierungs- und Beibehaltungsphasen sowohl zur Build-Zeit der Bibliothek als auch der App berücksichtigen, bevor Sie die Bibliothek optimieren.
Wenn Sie Ihre Bibliothek dennoch zur Build-Zeit optimieren möchten, wird dies vom Android-Gradle-Plug-in unterstützt.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Das Verhalten von proguardFiles unterscheidet sich stark von consumerProguardFiles:
proguardFileswerden zur Build-Zeit verwendet, oft zusammen mitgetDefaultProguardFile("proguard-android-optimize.txt"), um zu definieren, welcher Teil Ihrer Bibliothek während des Bibliotheks-Builds beibehalten werden soll. Mindestens ist dies Ihre öffentliche API.consumerProguardFileswerden dagegen in die Bibliothek verpackt, um die Optimierungen zu beeinflussen, die später während des Builds einer App erfolgen, die Ihre Bibliothek verwendet.
Wenn Ihre Bibliothek beispielsweise Reflection verwendet, um interne Klassen zu erstellen, müssen Sie die Keep-Regeln möglicherweise sowohl in proguardFiles als auch in consumerProguardFiles definieren.
Wenn Sie -repackageclasses im Build Ihrer Bibliothek verwenden, verpacken Sie Klassen in ein Unterpaket innerhalb des Pakets Ihrer Bibliothek. Verwenden Sie beispielsweise -repackageclasses
'com.example.mylibrary.internal' anstelle von -repackageclasses 'internal'.
Verschiedene R8-Versionen unterstützen (erweitert)
Sie können Regeln an bestimmte Versionen von R8 anpassen. So kann Ihre Bibliothek optimal in Projekten mit neueren R8-Versionen funktionieren, während vorhandene Regeln weiterhin in Projekten mit älteren R8-Versionen verwendet werden können.
Wenn Sie gezielte R8-Regeln angeben möchten, müssen Sie sie im Verzeichnis META-INF/com.android.tools in classes.jar einer AAR-Datei oder im Verzeichnis META-INF/com.android.tools einer JAR-Datei einfügen.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
Im META-INF/com.android.tools Verzeichnis können sich mehrere
Unterverzeichnisse mit Namen im Format r8-from-<X>-upto-<Y> befinden, um anzugeben,
für welche R8-Versionen die Regeln geschrieben wurden. Jedes Unterverzeichnis kann eine oder mehrere Dateien mit den R8-Regeln enthalten, mit beliebigen Dateinamen und Erweiterungen.
Beachten Sie, dass die -from-<X> und -upto-<Y> Teile optional sind, die <Y> Version
ist exklusiv und die Versionsbereiche in der Regel fortlaufend sind, können sich aber auch
überschneiden.
`r8`, `r8-upto-8.0.0`, `r8-from-8.0.0-upto-8.2.0` und `
r8-from-8.2.0` sind beispielsweise Verzeichnisnamen, die eine Reihe gezielter R8-Regeln darstellen. Die Regeln im Verzeichnis r8 können von allen R8-Versionen verwendet werden. Die Regeln unter
dem r8-from-8.0.0-upto-8.2.0 Verzeichnis können von R8 ab Version
8.0.0 bis einschließlich nicht einschließlich Version 8.2.0 verwendet werden.
Das Android-Gradle-Plug-in wählt anhand dieser Informationen alle Regeln aus, die von der aktuellen R8-Version verwendet werden können. Wenn in einer Bibliothek keine gezielten R8
Regeln angegeben sind, wählt das Android-Gradle-Plug-in die Regeln aus den Legacy-Speicherorten
(proguard.txt für eine AAR-Datei oder META-INF/proguard/<ProGuard-rule-files> für eine
JAR-Datei).