Optimierung für Bibliotheksautoren

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 verwenden können:

  • Regeln zum Beibehalten von Verbraucherdaten müssen Regeln angeben, die das beibehalten, was die Bibliothek widerspiegelt. Wenn eine Bibliothek Reflection oder JNI verwendet, um ihren Code oder Code aufzurufen, der von einer Client-App definiert wird, müssen diese Regeln beschreiben, welcher Code beibehalten werden muss. Bibliotheken sollten Keep-Regeln für Nutzer enthalten, die dasselbe Format wie Keep-Regeln für Apps verwenden. Diese Regeln werden in Bibliotheksartefakten (AARs oder JARs) gebündelt und bei der Optimierung von Android-Apps automatisch verwendet, wenn die Bibliothek verwendet wird. Diese Regeln werden in der Datei verwaltet, die mit dem Attribut consumerProguardFiles in Ihrer Datei build.gradle.kts (oder build.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 verhindern, dass die öffentliche API der Bibliothek 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 proguardFiles in Ihrer Datei build.gradle.kts (oder build.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 die App-Qualität und die Nutzerfreundlichkeit nicht erheblich zu beeinträchtigen.

  • Keine allgemeinen oder paketweiten Keep-Regeln:Ihre Bibliothek darf keine allgemeinen Keep-Regeln enthalten, die den Großteil des Codes in Ihrer Bibliothek oder in einer anderen Bibliothek beibehalten. 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 -dontobfuscate oder -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. Die Codegenerierung 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 Kompilierung keine größeren Änderungen 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 zur Laufzeit dynamisch über 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.

  • Unterstützung des vollständigen R8-Modus:Ihre Bibliothek darf 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, 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 -repackageclasses nicht in der Datei mit den Regeln zum Beibehalten von Consumer-Code für Ihre Bibliothek. Um den Build Ihrer Bibliothek zu optimieren, können Sie jedoch -repackageclasses mit einem internen Paketnamen wie <your.library.package>.internal in 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 eine Überschneidung mit den in proguard-android-optimize.txt definierten 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 die Nutzung Ihrer Bibliothek beibehalten:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Bibliotheksautoren sollten das Attribut RuntimeVisibleAnnotations in ihren Keep-Regeln für Nutzer beibehalten, wenn Anmerkungen zur Laufzeit verwendet werden.
  • Bibliotheksautoren sollten die folgenden globalen Optionen nicht in ihren Regeln zum Beibehalten von Consumer-Code verwenden:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Wann Reflexion in Ordnung ist

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 Verbrauchern 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. Im Gegensatz dazu ist die offene Reflexion 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 R8-Optimierungen: Entgegen der landläufigen Meinung beschränken sich die R8-Optimierungen nicht nur auf die Verschleierung, sondern umfassen auch die Codekomprimierung 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)Format optimiert oder verschleiert wurde. Die Optimierungen während der Erstellung 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 -keep verhindert, dass R8 seine Optimierungsdurchläufe ausführt. Weitere Informationen finden Sie unter Die richtige Aufbewahrungsoption auswählen.

Regelverpackung konfigurieren

Damit Ihre Regeln zur Aufbewahrung von Verbraucherdaten korrekt angewendet werden, müssen Sie sie je nach Bibliotheksformat entsprechend verpacken.

AAR-Bibliotheken

Wenn Sie Consumer-Regeln für eine AAR-Bibliothek hinzufügen möchten, verwenden Sie die Option consumerProguardFiles im Build-Script 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 am richtigen Speicherort in Ihrer Ausgabedatei vom Typ „JAR“ gebündelt.

Prüfen Sie, ob die Regeln im endgültigen JAR-Bundle korrekt sind, indem Sie nachsehen, ob sie sich 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:

  • proguardFiles werden zur Build-Zeit verwendet, oft zusammen mit getDefaultProguardFile("proguard-android-optimize.txt"), um festzulegen, welcher Teil Ihrer Bibliothek während des Bibliotheks-Build beibehalten werden soll. Das ist mindestens Ihre öffentliche API.
  • consumerProguardFiles werden dagegen in der Bibliothek verpackt, um zu beeinflussen, welche Optimierungen später beim Erstellen 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, 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.

Beachten Sie, dass die Teile -from-<X> und -upto-<Y> optional sind. Die <Y>-Version ist exklusiv. Die Versionsbereiche sind in der Regel fortlaufend, können sich aber auch überschneiden.

Beispiele: r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 und r8-from-8.2.0 sind Verzeichnisnamen, die eine Reihe von R8-Zielregeln darstellen. 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 das Ziel angegeben sind, wählt das Android-Gradle-Plug-in die Regeln aus den alten Speicherorten aus (proguard.txt für eine AAR oder META-INF/proguard/<ProGuard-rule-files> für eine JAR).