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 bei der Programmierung 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 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 Verbrauchern geschrieben werden.

Diese spezifische und gezielte Form der Reflection 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 Reflection 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 verwenden 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, 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 Verbraucherdaten schreiben.
  • Die Regeln zum Beibehalten von 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 der Eigenschaft proguardFiles in Ihrer Datei build.gradle.kts (oder build.gradle) angegeben ist. Weitere Informationen finden Sie unter AAR-Bibliotheksbuild optimieren.

Regeln zum Beibehalten von Nutzerinhalten schreiben

Zusätzlich zu den allgemeinen Regeln zum Beibehalten 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.
  • Verwenden Sie -repackageclasses nicht in der Datei mit den Aufbewahrungsregeln für Nutzer Ihrer Bibliothek. Um den Bibliotheksbuild 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 Apps auch 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 Überschneidungen mit den in proguard-android-optimize.txt definierten Attributen gibt.
  • Wenn Sie die folgenden Attribute in Ihrer Bibliotheksverteilung benötigen, behalten Sie sie in der Datei mit den Keep-Regeln für den Build Ihrer Bibliothek bei und nicht in der Datei mit den Keep-Regeln für den Consumer Ihrer Bibliothek:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Bibliotheksautoren sollten das Attribut RuntimeVisibleAnnotations in ihren Keep-Regeln für Consumer 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 einer AAR-Bibliothek Consumer-Regeln 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 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 am richtigen Speicherort in Ihrer Ausgabedatei vom Typ „JAR“ gebündelt.

Prüfen Sie, ob die Regeln im endgültigen JAR-Bundle korrekt sind. Die Regeln sollten 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. Erst beim Erstellen 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. Mindestens ist dies 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 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.

Die Teile -from-<X> und -upto-<Y> sind optional, die <Y>-Version ist exklusiv und 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 die Zielgruppe angegeben sind, wählt das Android-Gradle-Plugin die Regeln aus den alten Speicherorten aus (proguard.txt für eine AAR-Datei oder META-INF/proguard/<ProGuard-rule-files> für eine JAR-Datei).