Optimierung für Bibliotheksautoren

Als Bibliotheksautor sollten Sie dafür sorgen, dass App-Entwickler Ihre Bibliothek problemlos in ihre App einbinden können und gleichzeitig eine hohe Nutzerfreundlichkeit gewährleistet ist. Sie sollten darauf achten, dass Ihre Bibliothek ohne zusätzliche Einrichtung mit der Android-Optimierung kompatibel ist, oder dokumentieren, dass die Bibliothek möglicherweise nicht für die Verwendung auf Android geeignet ist.

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.

Codegenerierung statt Reflektion 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 Kompilierung 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 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.

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 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. Im Gegensatz dazu ist die offene Reflexion von Gson nicht für die Verwendung in Android-Apps geeignet.

Arten von Aufbewahrungsregeln in Bibliotheken

Es gibt zwei verschiedene Arten von Aufbewahrungsregeln, die Sie in Bibliotheken haben können:

  • Bei Regeln zum Beibehalten von Inhalten müssen Regeln angegeben werden, die sich auf die Inhalte beziehen, die in der Bibliothek enthalten sind. 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 automatisch während der Optimierung von Android-Apps 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.
  • Die Regeln zum Beibehalten von Builds für Bibliotheken 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, 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 dem Attribut proguardFiles in Ihrer Datei build.gradle.kts (oder build.gradle) angegeben ist. Weitere Informationen finden Sie unter AAR-Bibliotheks-Build optimieren.

Regeln zum Beibehalten von Nutzerdaten schreiben

Neben den allgemeinen Best Practices für Aufbewahrungsregeln gelten die folgenden Empfehlungen speziell für Bibliotheksautoren.

  • Verwenden Sie keine unangemessenen globalen Regeln. Vermeiden Sie es, globale Einstellungen wie -dontobfuscate oder -allowaccessmodification in die Datei mit den Regeln zum Beibehalten für die Nutzer Ihrer Bibliothek aufzunehmen, da sie sich auf alle Apps auswirken, die Ihre Bibliothek verwenden.
  • Geben Sie keine paketweiten Aufbewahrungsregeln wie -keep class com.mylibrary.** { *; } an. Solche Regeln schränken die Optimierung der gesamten Bibliothek ein und wirken sich auf die Größe aller Apps aus, die die Bibliothek verwenden.
  • 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 Ihre Bibliothek effizienter werden, auch wenn die Apps, die sie verwenden, nicht optimiert sind. Im Allgemeinen ist das jedoch nicht erforderlich, da auch Apps optimiert werden sollten. Weitere Informationen zum Optimieren von Bibliotheken finden Sie unter Optimierung für Bibliotheksautoren.
  • 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 den Consumer 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 Keep-Regeln für Nutzer verwenden:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

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 mit Ihrer Kotlin-/Java-Bibliothek bündeln möchten, die als JAR ausgeliefert wird, legen Sie die Regelfile mit einem beliebigen Dateinamen im Verzeichnis META-INF/proguard/ der endgültigen JAR-Datei ab. 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. Erst während des Builds einer Anwendung, wenn eine Bibliothek als Teil einer Anwendung enthalten ist, kann R8 erkennen, wie alle Methoden der Bibliothek verwendet werden und welche Parameter übergeben werden. Als Bibliotheksentwickler müssen Sie mehrere Optimierungsphasen berücksichtigen und das Verhalten sowohl zur Bibliotheks- als auch zur App-Build-Zeit im Blick behalten, 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, 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).