Als Bibliotheksautor müssen Sie dafür sorgen, dass App-Entwickler Ihre Bibliothek problemlos in ihre App einbinden können und gleichzeitig eine hohe Nutzerfreundlichkeit gewährleistet wird. Das bedeutet, dass Ihre Bibliothek mit der Android-Optimierung (R8) kompatibel sein muss, ohne dass der Entwickler zusätzliche Einrichtungsschritte ausführen muss. Alternativ können Sie dokumentieren, dass die Bibliothek möglicherweise nicht für die Verwendung auf Android geeignet ist. Es ist wichtig, 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 sich für die Verwendung eignen, finden Sie unter Bibliotheken mit Bedacht auswählen.
Arten von Aufbewahrungsregeln
Es gibt zwei verschiedene Arten von Aufbewahrungsregeln, die Sie in Bibliotheken haben können:
- In Consumer-Keep-Regeln müssen Regeln angegeben werden, die das behalten, was in der Bibliothek enthalten ist. Wenn eine Bibliothek Reflection oder JNI verwendet, um ihren Code oder Code aufzurufen, der von einer Client-App definiert wird, muss in diesen Regeln beschrieben werden, welcher Code beibehalten werden muss. Bibliotheken sollten Consumer-Keep-Regeln enthalten, die dasselbe Format wie App-Keep-Regeln verwenden. Diese Regeln sind in Bibliotheksartefakten (AARs oder JARs) enthalten und werden bei der Optimierung von Android-Apps automatisch verwendet, wenn die Bibliothek verwendet wird. Diese Regeln werden in der Datei verwaltet, die mit dem Attribut
consumerProguardFilesin Ihrer Dateibuild.gradle.kts(oderbuild.gradle) angegeben ist. Weitere Informationen finden Sie unter Regeln zum Beibehalten von Verbrauchern schreiben. - Regeln zum Beibehalten von Elementen beim Erstellen der Mediathek werden angewendet, wenn Ihre Mediathek erstellt wird. Sie sind nur erforderlich, wenn Sie Ihre Bibliothek zur Build-Zeit teilweise optimieren möchten. Sie müssen dafür sorgen, dass die öffentliche API der Bibliothek nicht entfernt wird, da sie sonst nicht in der Bibliotheksverteilung enthalten ist und App-Entwickler die Bibliothek nicht verwenden können. Diese Regeln werden in der Datei verwaltet, die mit der Eigenschaft
proguardFilesin Ihrer Dateibuild.gradle.kts(oderbuild.gradle) angegeben ist. Weitere Informationen finden Sie unter AAR-Bibliotheks-Build optimieren.
Anforderungen und Richtlinien für die Optimierung
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-Regeln müssen Bibliotheksautoren bestimmte Anforderungen einhalten und zusätzliche Richtlinien berücksichtigen.
Optimierungsanforderungen erfüllen
Ineffizienz in Bibliotheken trägt maßgeblich zu App-Bloat, verschwendetem Speicher, langsamen Starts und ANRs („App antwortet nicht“-Fehlern) bei. Bibliotheken dürfen die folgenden Anforderungen nicht verletzen, um eine erhebliche Beeinträchtigung der App-Qualität und der Nutzerfreundlichkeit zu vermeiden.
Keine umfassenden oder paketweiten Keep-Regeln:Ihre Bibliothek darf keine umfassenden Keep-Regeln enthalten, mit denen der Großteil des Codes in Ihrer Bibliothek oder in einer anderen Bibliothek beibehalten wird. Umfassende Keep-Regeln können kurzfristig Abstürze beheben, aber sie erhöhen die App-Größe aller Apps, die Ihre Bibliothek verwenden.
Fügen Sie keine paketweiten Keep-Regeln (z. B.
-keep class com.mylibrary.** {*; }) für Pakete in Ihrer Bibliothek oder anderen referenzierten Bibliotheken ein. Solche Regeln schränken die Optimierung für diese Pakete in allen Apps ein, in denen Ihre Bibliothek verwendet wird.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. Codegen und Reflection sind beides gängige Ansätze, um Boilerplate-Code beim Programmieren zu vermeiden. Codegen ist jedoch besser mit einem App-Optimierer wie R8 kompatibel.
Bei der Codegenerierung wird Code während des Build-Prozesses analysiert und geändert. Da nach der Kompilierungszeit keine größeren Änderungen mehr vorgenommen werden, weiß der Optimierer, welcher Code letztendlich benötigt wird und welcher sicher entfernt werden kann.
Bei der Reflektion wird Code zur Laufzeit analysiert und bearbeitet. Da der Code erst bei der Ausführung wirklich fertiggestellt wird, weiß der Optimierer nicht, welcher Code gefahrlos entfernt werden kann. Dabei wird wahrscheinlich Code entfernt, der während der Laufzeit dynamisch durch Reflection verwendet wird. Das führt zu App-Abstürzen bei Nutzern.
Viele moderne Bibliotheken verwenden Codegenerierung anstelle von Reflection. KSP ist ein gemeinsamer Einstiegspunkt, der von Room, Dagger2 und vielen anderen verwendet wird.
R8-Vollmodus unterstützen:Ihre Bibliothek darf nicht abstürzen, wenn der R8-Vollmodus aktiviert ist. Der vollständige Modus von R8 ist der empfohlene Modus für die Verwendung von R8 und ist seit AGP 8.0, das 2023 stabil wurde, der Standardmodus. Wenn Ihre Bibliothek unter R8 abstürzt, müssen Sie den spezifischen Reflexions- oder JNI-Einstiegspunkt ermitteln und eine gezielte Regel hinzufügen, anstatt das gesamte Paket beizubehalten.
Weitere Empfehlungen
Neben den Optimierungsanforderungen gelten die folgenden zusätzlichen Empfehlungen.
- Verwenden Sie
-repackageclassesnicht in der Datei mit den Regeln zum Beibehalten von Consumer-Code für Ihre Bibliothek. Um den Bibliotheksbuild zu optimieren, können Sie jedoch-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. Das ist jedoch in der Regel nicht erforderlich, da Apps auch optimiert werden sollten. - Deklarieren Sie alle Attribute, die für die Funktion Ihrer Bibliothek erforderlich sind, in den Keep-Regeldateien Ihrer Bibliothek, auch wenn es Überschneidungen mit den in
proguard-android-optimize.txtdefinierten Attributen gibt. - Wenn Sie die folgenden Attribute in Ihrer Bibliotheksverteilung benötigen, müssen Sie sie in der Datei mit den Keep-Regeln für den Build Ihrer Bibliothek und nicht in der Datei mit den Keep-Regeln für den Consumer Ihrer Bibliothek beibehalten:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Bibliotheksautoren sollten das Attribut
RuntimeVisibleAnnotationsin ihren Keep-Regeln für Nutzer beibehalten, wenn Anmerkungen zur Laufzeit verwendet werden. - Bibliotheksautoren sollten die folgenden globalen Optionen nicht in ihren Keep-Regeln für Nutzer verwenden:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Wann Reflexionen in Ordnung sind
Wenn Sie Reflection verwenden müssen, sollten Sie nur in eine der folgenden Klassen reflektieren:
- Bestimmte Zieltypen (bestimmte Schnittstellenimplementierungen oder Unterklassen)
- Code mit einer bestimmten Laufzeitannotation
Durch die Verwendung von Reflection auf diese Weise werden die Laufzeitkosten begrenzt und es können zielgerichtete Regeln zum Beibehalten von Consumer-Code geschrieben werden.
Diese spezifische und gezielte Form der Reflexion ist ein Muster, das sowohl im Android-Framework (z. B. beim Inflating von Aktivitäten, Ansichten und Drawables) als auch in AndroidX-Bibliotheken (z. B. beim Erstellen von WorkManager
ListenableWorkers oder RoomDatabases) zu sehen ist. Die offene Reflexion von Gson ist dagegen 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 R8-Optimierungen: Entgegen der landläufigen Meinung beschränken sich die R8-Optimierungen nicht nur auf die Verschleierung, sondern umfassen auch das Verkleinern von Code und logische Optimierungen mit Methoden-Inlining und Techniken zum Zusammenführen von Klassen. Weitere Informationen finden Sie unter R8-Optimierung – Übersicht.
Optimierung von verschleierten Bibliotheken umgehen: Ein häufiger Fehler ist, eine Bibliothek von der Optimierung auszuschließen, weil sie beim Kompilieren in ein AAR- (Android Archive) oder JAR- (Java Archive) optimiert oder verschleiert wurde. Die Optimierungen während der Build-Zeit der Bibliothek sind begrenzt. Ihre App sollte die Optimierung der Bibliothek nicht deaktivieren, indem sie in eine Keep-Regel aufgenommen wird. Weitere Informationen finden Sie unter AAR-Bibliotheksbuild optimieren.
Falsches Verständnis der Option
-keep: Die Regel-keepverhindert, dass R8 seine Optimierungsdurchläufe ausführt. Weitere Informationen finden Sie unter Die richtige Aufbewahrungsoption auswählen.
Regelpaketierung konfigurieren
Damit Ihre Regeln zur Aufbewahrung von Verbraucherdaten korrekt angewendet werden, müssen Sie sie je nach Bibliotheksformat entsprechend verpacken.
AAR-Bibliotheken
Wenn Sie einem AAR-Bibliotheksmodul Consumer-Regeln hinzufügen möchten, verwenden Sie die Option consumerProguardFiles im Build-Skript des Android-Bibliotheksmoduls. 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 in Ihre Kotlin- oder Java-Bibliothek aufnehmen möchten, die als JAR-Datei ausgeliefert wird, legen Sie die Regelfile mit einem beliebigen Dateinamen in das Verzeichnis META-INF/proguard/ der endgültigen JAR-Datei.
Wenn sich Ihr Code beispielsweise in <libraryroot>/src/main/kotlin befindet, legen Sie eine Datei mit Verbraucherregeln unter <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro ab. Die Regeln werden dann an der richtigen Stelle in Ihrer Ausgabedatei vom Typ „JAR“ gebündelt.
Prüfen Sie, ob die Regeln im endgültigen JAR-Bundle korrekt sind. Dazu müssen Sie prüfen, ob sich die Regeln im Verzeichnis META-INF/proguard befinden.
Erstellen der AAR-Bibliothek optimieren (erweitert)
Im Allgemeinen müssen Sie einen Bibliotheksbuild nicht direkt optimieren, da die möglichen Optimierungen während des Bibliotheksbuilds sehr begrenzt sind. Als Bibliotheksentwickler müssen Sie mehrere Optimierungsphasen durchlaufen und das Verhalten sowohl zur Bibliotheks- als auch zur App-Build-Zeit berücksichtigen, bevor Sie die Bibliothek optimieren.
Wenn Sie Ihre Bibliothek weiterhin 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 dem von consumerProguardFiles:
proguardFileswerden zur Build-Zeit verwendet, oft zusammen mitgetDefaultProguardFile("proguard-android-optimize.txt"), um festzulegen, welcher Teil Ihrer Bibliothek während des Bibliotheks-Build beibehalten werden soll. Das ist mindestens Ihre öffentliche API.consumerProguardFileswerden dagegen in der Bibliothek verpackt, um zu beeinflussen, welche Optimierungen später beim Erstellen einer App, die Ihre Bibliothek verwendet, erfolgen.
Wenn Ihre Bibliothek beispielsweise Reflection zum Erstellen interner Klassen verwendet, müssen Sie die Keep-Regeln möglicherweise sowohl in proguardFiles als auch in consumerProguardFiles definieren.
Wenn Sie -repackageclasses im Build Ihrer Bibliothek verwenden, packen Sie die Klassen in ein Unterpaket innerhalb des Pakets Ihrer Bibliothek um. Verwenden Sie z. B. -repackageclasses
'com.example.mylibrary.internal' statt -repackageclasses 'internal'.
Verschiedene R8-Versionen unterstützen (erweitert)
Sie können Regeln so anpassen, dass sie auf bestimmte Versionen von R8 ausgerichtet sind. So kann Ihre Bibliothek optimal in Projekten mit neueren R8-Versionen verwendet werden, 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 in das Verzeichnis META-INF/com.android.tools innerhalb von classes.jar einer AAR-Datei oder in das 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 Verzeichnis META-INF/com.android.tools kann es mehrere Unterverzeichnisse mit Namen im Format r8-from-<X>-upto-<Y> geben, um anzugeben, für welche R8-Versionen die Regeln geschrieben wurden. Jedes Unterverzeichnis kann eine oder mehrere Dateien mit den R8-Regeln enthalten. Die Dateinamen und ‑erweiterungen sind beliebig.
Die Teile -from-<X> und -upto-<Y> sind optional. Die <Y>-Version ist exklusiv. Die Versionsbereiche sind in der Regel fortlaufend, können sich aber auch überschneiden.
Beispiele für Verzeichnisnamen, die eine Reihe von R8-Zielregeln darstellen, sind r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 und r8-from-8.2.0. Die Regeln im Verzeichnis r8 können von allen R8-Versionen verwendet werden. Die Regeln im Verzeichnis r8-from-8.0.0-upto-8.2.0 können von R8 ab Version 8.0.0 bis einschließlich Version 8.2.0 verwendet werden.
Das Android-Gradle-Plug-in verwendet diese Informationen, um alle Regeln auszuwählen, die von der aktuellen R8-Version verwendet werden können. Wenn in einer Bibliothek keine R8-Regeln für die Zielgruppe angegeben sind, wählt das Android-Gradle-Plugin die Regeln aus den alten Speicherorten aus (proguard.txt für eine AAR oder META-INF/proguard/<ProGuard-rule-files> für eine JAR).