App verkleinern, verschleiern und optimieren

Damit Ihre App so klein und schnell wie möglich ist, sollten Sie den Release-Build mit isMinifyEnabled = true optimieren und komprimieren.

Dadurch wird die Verkleinerung aktiviert, wodurch nicht verwendeter Code und nicht verwendete Ressourcen entfernt werden, die Verschleierung, mit der die Namen der Klassen und Mitglieder Ihrer App gekürzt werden, sowie die Optimierung, die aggressivere Strategien anwendet, um die Größe und Leistung Ihrer Anwendung weiter zu reduzieren und die Leistung Ihrer Anwendung zu verbessern. Auf dieser Seite wird beschrieben, wie R8 diese Kompilierungszeitaufgaben für Ihr Projekt ausführt und wie Sie diese anpassen können.

Wenn Sie Ihr Projekt mit dem Android-Gradle-Plug-in 3.4.0 oder höher erstellen, verwendet das Plug-in nicht mehr ProGuard, um den Code für die Kompilierungszeit zu optimieren. Das Plug-in verwendet stattdessen den R8-Compiler, um die folgenden Aufgaben bei der Kompilierung zu verarbeiten:

  • Codeverkleinerung (oder Tree-Shaking): Erkennt nicht verwendete Klassen, Felder, Methoden und Attribute der Anwendung und deren Bibliotheksabhängigkeiten und entfernt sie sicher aus der Anwendung und ihren Bibliotheksabhängigkeiten. Damit ist sie ein wertvolles Tool, um das Referenzlimit von 64.000 zu umgehen. Wenn Sie beispielsweise nur einige APIs einer Bibliotheksabhängigkeit verwenden, kann die Verkleinerung den Bibliothekscode ermitteln, den Ihre App nicht verwendet, und nur diesen Code aus der App entfernen. Weitere Informationen finden Sie im Abschnitt zum Verkleinern von Code.
  • Ressourcenverkleinerung:Entfernt nicht verwendete Ressourcen aus der gepackten Anwendung, einschließlich nicht verwendeter Ressourcen aus den Bibliotheksabhängigkeiten der Anwendung. Es funktioniert in Verbindung mit der Codekomprimierung, sodass nach dem Entfernen von ungenutztem Code auch Ressourcen, auf die nicht mehr verwiesen wird, sicher entfernt werden können. Weitere Informationen finden Sie im Abschnitt zum Reduzieren von Ressourcen.
  • Optimierung:Überprüft und umschreibt Ihren Code, um die Laufzeitleistung zu verbessern und die Größe der DEX-Dateien Ihrer Anwendung weiter zu reduzieren. Dadurch wird die Laufzeitleistung des Codes um bis zu 30 % verbessert und das Start- und Frame-Timing erheblich verbessert. Wenn R8 beispielsweise erkennt, dass der Branch else {} für eine bestimmte if/else-Anweisung nie verwendet wird, entfernt R8 den Code für den else {}-Branch. Weitere Informationen finden Sie im Abschnitt zur Codeoptimierung.
  • Verschleierung (oder Reduzierung von Kennungen): Die Namen von Klassen und Mitgliedern werden gekürzt, was zu einer reduzierten DEX-Dateigröße führt. Weitere Informationen finden Sie im Abschnitt zur Verschleierung von Code.

Beim Erstellen der Releaseversion Ihrer Anwendung kann R8 so konfiguriert werden, dass die oben beschriebenen Aufgaben zur Kompilierungszeit für Sie ausgeführt werden. Sie können auch bestimmte Aufgaben deaktivieren oder das Verhalten von R8 über ProGuard-Regeldateien anpassen. R8 funktioniert mit allen Ihren vorhandenen ProGuard-Regeldateien. Wenn Sie das Android-Gradle-Plug-in zur Verwendung von R8 aktualisieren, sollten Sie also keine vorhandenen Regeln ändern.

Verkleinerung, Verschleierung und Optimierung aktivieren

Wenn Sie Android Studio 3.4 oder das Android-Gradle-Plug-in 3.4.0 und höher verwenden, ist R8 der Standard-Compiler, der den Java-Bytecode Ihres Projekts in das DEX-Format umwandelt, das auf der Android-Plattform ausgeführt wird. Wenn Sie jedoch ein neues Projekt mit Android Studio erstellen, sind Verkleinerung, Verschleierung und Codeoptimierung standardmäßig nicht aktiviert. Das liegt daran, dass diese Optimierungen bei der Kompilierungszeit die Build-Dauer Ihres Projekts erhöhen und Fehler verursachen können, wenn Sie nicht ausreichend selbst festlegen, welchen Code beibehalten werden soll.

Daher ist es am besten, diese Aufgaben während der Kompilierung zu aktivieren, wenn du die endgültige Version deiner App erstellst, die du vor der Veröffentlichung testest. Um die Verkleinerung, Verschleierung und Optimierung zu aktivieren, fügen Sie Folgendes in Ihr Build-Skript auf Projektebene ein.

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `isDebuggable=false`.
            isMinifyEnabled = true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            isShrinkResources = true

            proguardFiles(
                // Includes the default ProGuard rules files that are packaged with
                // the Android Gradle plugin. To learn more, go to the section about
                // R8 configuration files.
                getDefaultProguardFile("proguard-android-optimize.txt"),

                // Includes a local, custom Proguard rules file
                "proguard-rules.pro"
            )
        }
    }
    ...
}

Cool

android {
    buildTypes {
        release {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `debuggable false`.
            minifyEnabled true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            shrinkResources true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}

R8-Konfigurationsdateien

R8 verwendet ProGuard-Regeldateien, um das Standardverhalten zu ändern und die Struktur Ihrer App besser zu verstehen, z. B. die Klassen, die als Einstiegspunkte in Ihren App-Code dienen. Auch wenn Sie einige dieser Regeldateien ändern können, werden einige Regeln möglicherweise von Tools zur Kompilierungszeit (z. B. AAPT2) automatisch generiert oder von den Bibliotheksabhängigkeiten Ihrer Anwendung übernommen. In der folgenden Tabelle werden die Quellen von ProGuard-Regeldateien beschrieben, die R8 verwendet.

Quelle Standort Beschreibung
Android Studio <module-dir>/proguard-rules.pro Wenn Sie ein neues Modul mit Android Studio erstellen, erstellt die IDE eine proguard-rules.pro-Datei im Stammverzeichnis dieses Moduls.

Standardmäßig werden auf diese Datei keine Regeln angewendet. Fügen Sie hier also Ihre eigenen ProGuard-Regeln ein, z. B. Ihre benutzerdefinierten Notizen.

Android-Gradle-Plug-in Wird vom Android-Gradle-Plug-in bei der Kompilierung generiert. Das Android-Gradle-Plug-in generiert proguard-android-optimize.txt. Diese enthält Regeln, die für die meisten Android-Projekte nützlich sind, und aktiviert @Keep*-Annotationen.

Wenn Sie mit Android Studio ein neues Modul erstellen, nimmt das Build-Skript auf Modulebene diese Regeldatei standardmäßig in Ihren Release-Build auf.

Hinweis:Das Android-Gradle-Plug-in enthält zusätzliche vordefinierte ProGuard-Regeldateien. Wir empfehlen jedoch die Verwendung von proguard-android-optimize.txt.

Bibliotheksabhängigkeiten AAE-Bibliotheken: <library-dir>/proguard.txt

JAR-Bibliotheken: <library-dir>/META-INF/proguard/

Wenn eine AAR-Bibliothek mit einer eigenen ProGuard-Regeldatei veröffentlicht wird und Sie diese AAR als Kompilierungszeitabhängigkeit einschließen, wendet R8 beim Kompilieren Ihres Projekts automatisch seine Regeln an.

Die Verwendung von Regeldateien, die mit AAR-Bibliotheken gepackt sind, ist nützlich, wenn bestimmte Keep-Regeln erforderlich sind, damit die Bibliothek ordnungsgemäß funktioniert, d. h. wenn der Bibliotheksentwickler die Schritte zur Fehlerbehebung für Sie durchgeführt hat.

Da ProGuard-Regeln additiv sind, können bestimmte Regeln, die in einer AAR-Bibliotheksabhängigkeit eingeschlossen sind, nicht entfernt werden und sich auf die Kompilierung anderer Teile Ihrer Anwendung auswirken. Wenn eine Bibliothek beispielsweise eine Regel zum Deaktivieren von Codeoptimierungen enthält, werden durch diese Regel Optimierungen für Ihr gesamtes Projekt deaktiviert.

Android Asset Package Tool 2 (AAPT2) Nachdem Sie Ihr Projekt mit minifyEnabled true erstellt haben: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 generiert Keep-Regeln auf Basis von Verweisen auf Klassen im Manifest, in Layouts und in anderen App-Ressourcen Ihrer App. AAPT2 enthält beispielsweise eine Aufbewahrungsregel für jede Aktivität, die du im Manifest deiner App als Einstiegspunkt registrierst.
Benutzerdefinierte Konfigurationsdateien Wenn Sie ein neues Modul mit Android Studio erstellen, erstellt die IDE standardmäßig <module-dir>/proguard-rules.pro für Sie, damit Sie Ihre eigenen Regeln hinzufügen können. Sie können zusätzliche Konfigurationen hinzufügen, die dann in R8 bei der Kompilierung angewendet werden.

Wenn Sie das Attribut minifyEnabled auf true setzen, kombiniert R8 Regeln aus allen oben aufgeführten verfügbaren Quellen. Dies ist bei der Fehlerbehebung mit R8 wichtig, da andere Abhängigkeiten zur Kompilierungszeit (z. B. Bibliotheksabhängigkeiten) zu Änderungen im R8-Verhalten führen können, die Sie nicht kennen.

Um einen vollständigen Bericht aller Regeln zu erhalten, die R8 beim Erstellen Ihres Projekts anwendet, fügen Sie Folgendes in die Datei proguard-rules.pro Ihres Moduls ein:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Zusätzliche Konfigurationen hinzufügen

Wenn Sie mit Android Studio ein neues Projekt oder Modul erstellen, erstellt die IDE eine <module-dir>/proguard-rules.pro-Datei, in der Sie Ihre eigenen Regeln einfügen können. Sie können auch zusätzliche Regeln aus anderen Dateien einbinden. Dazu fügen Sie sie dem Attribut proguardFiles im Build-Skript Ihres Moduls hinzu.

Sie können beispielsweise Regeln hinzufügen, die für jede Build-Variante spezifisch sind, indem Sie ein weiteres proguardFiles-Attribut in den entsprechenden productFlavor-Block einfügen. Die folgende Gradle-Datei fügt dem flavor2-Produkt-Flavor flavor2-rules.pro hinzu. flavor2 verwendet jetzt alle drei ProGuard-Regeln, da auch diejenigen aus dem Block release angewendet werden.

Außerdem kannst du das Attribut testProguardFiles hinzufügen, das eine Liste von ProGuard-Dateien angibt, die nur im Test-APK enthalten sind:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                "proguard-rules.pro"
            )
            testProguardFiles(
                // The proguard files listed here are included in the
                // test APK only.
                "test-proguard-rules.pro"
            )
        }
    }
    flavorDimensions.add("version")
    productFlavors {
        create("flavor1") {
            ...
        }
        create("flavor2") {
            proguardFile("flavor2-rules.pro")
        }
    }
}

Cool

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
            testProguardFiles
                // The proguard files listed here are included in the
                // test APK only.
                'test-proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

Code verkleinern

Das Verkleinern von Code mit R8 ist standardmäßig aktiviert, wenn Sie das Attribut minifyEnabled auf true festlegen.

Codeverkleinerung (auch als Tree Shaking bezeichnet) ist der Vorgang zum Entfernen von Code, von dem R8 feststellt, dass er zur Laufzeit nicht erforderlich ist. Dieser Vorgang kann die Größe Ihrer Anwendung erheblich reduzieren, wenn sie beispielsweise viele Bibliotheksabhängigkeiten enthält, aber nur einen kleinen Teil ihrer Funktionalität nutzt.

Zum Verkleinern des Anwendungscodes ermittelt R8 zuerst alle Einstiegspunkte in den Code Ihrer Anwendung anhand der kombinierten Konfigurationsdateien. Diese Einstiegspunkte umfassen alle Klassen, die die Android-Plattform möglicherweise zum Öffnen der Aktivitäten oder Dienste Ihrer App verwenden kann. Ausgehend von jedem Einstiegspunkt prüft R8 den Code Ihrer Anwendung, um ein Diagramm aller Methoden, Mitgliedsvariablen und anderen Klassen zu erstellen, auf die Ihre Anwendung während der Laufzeit zugreifen kann. Code, der nicht mit dieser Grafik verknüpft ist, wird als nicht erreichbar betrachtet und kann aus der App entfernt werden.

Abbildung 1 zeigt eine Anwendung mit einer Laufzeitbibliothekabhängigkeit. Bei der Überprüfung des Anwendungscodes ermittelt R8, dass die Methoden foo(), faz() und bar() vom Einstiegspunkt MainActivity.class aus erreichbar sind. Die Klasse OkayApi.class oder ihre Methode baz() wird jedoch von Ihrer Anwendung zur Laufzeit nie verwendet und R8 entfernt diesen Code, wenn die Anwendung verkleinert wird.

Abbildung 1: Zum Zeitpunkt der Kompilierung erstellt R8 ein Diagramm auf der Grundlage der kombinierten Keep-Regeln Ihres Projekts, um nicht erreichbaren Code zu ermitteln.

R8 bestimmt Einstiegspunkte über -keep-Regeln in den R8-Konfigurationsdateien des Projekts. Mit Keep-Regeln wird also angegeben, welche Klassen von R8 beim Verkleinern Ihrer App nicht verworfen werden sollen. R8 betrachtet diese Klassen als mögliche Einstiegspunkte in Ihrer App. Das Android-Gradle-Plug-in und AAPT2 generieren automatisch Keep-Regeln, die von den meisten App-Projekten für Sie erforderlich sind, z. B. für die Aktivitäten, Ansichten und Dienste Ihrer App. Wenn Sie dieses Standardverhalten jedoch mit zusätzlichen Keep-Regeln anpassen möchten, lesen Sie den Abschnitt Festlegen, welcher Code beibehalten werden soll.

Wenn Sie stattdessen nur die Größe der Anwendungsressourcen reduzieren möchten, fahren Sie mit dem Abschnitt zum Reduzieren der Ressourcengröße fort.

Wenn ein Bibliotheksprojekt verkleinert ist, umfasst eine App, die von dieser Bibliothek abhängt, auch verkleinerte Bibliotheksklassen. Möglicherweise müssen Sie die Regeln für die Bibliotheksbeibehaltung anpassen, wenn im Bibliotheks-APK Klassen fehlen. Wenn Sie eine Bibliothek im AAR-Format erstellen und veröffentlichen, werden lokale JAR-Dateien, von denen Ihre Bibliothek abhängt, in der AAR-Datei nicht verkleinert.

Festlegen, welcher Code beibehalten werden soll

In den meisten Fällen reicht die Standarddatei von ProGuard (proguard-android- optimize.txt) für R8 aus, um nur den nicht verwendeten Code zu entfernen. In einigen Situationen ist es jedoch für R8 schwierig, sie richtig zu analysieren, sodass möglicherweise Code entfernt wird, den Ihre Anwendung tatsächlich benötigt. Beispiele für Fälle, in denen Code fälschlicherweise entfernt werden kann:

  • Wenn Ihre App eine Methode über die Java Native Interface (JNI) aufruft
  • Wenn Ihre App zur Laufzeit Code sucht (z. B. bei Reflexion)

Beim Testen der Anwendung sollten alle Fehler aufgedeckt werden, die durch nicht ordnungsgemäß entfernten Code verursacht wurden. Sie können jedoch auch prüfen, welcher Code entfernt wurde, indem Sie einen Bericht über entfernten Code generieren.

Fügen Sie der ProGuard-Regeldatei eine -keep-Zeile hinzu, um Fehler zu beheben und R8 dazu zu zwingen, bestimmten Code beizubehalten. Beispiel:

-keep public class MyClass

Alternativ können Sie die Annotation @Keep in den Code einfügen, den Sie beibehalten möchten. Wenn Sie @Keep einer Klasse hinzufügen, bleibt die gesamte Klasse unverändert. Wenn Sie sie einer Methode oder einem Feld hinzufügen, bleiben die Methode bzw. das Feld (und sein Name) sowie der Klassenname erhalten. Diese Annotation ist nur verfügbar, wenn Sie die AndroidX-Annotationsbibliothek verwenden und die ProGuard-Regeldatei einbeziehen, die mit dem Android-Gradle-Plug-in gepackt ist. Dies wird im Abschnitt zum Aktivieren der Verkleinerung beschrieben.

Bei der Verwendung der Option -keep sollten Sie eine Reihe von Überlegungen berücksichtigen. Weitere Informationen zum Anpassen Ihrer Regeldatei finden Sie im ProGuard-Handbuch. Im Abschnitt Fehlerbehebung werden weitere häufige Probleme beschrieben, die auftreten können, wenn Ihr Code entfernt wird.

Native Bibliotheken entfernen

Standardmäßig werden native Codebibliotheken in Release-Builds Ihrer App entfernt. Dabei werden die Symboltabelle und Debugging-Informationen entfernt, die in allen nativen Bibliotheken enthalten sind, die von Ihrer App verwendet werden. Das Entfernen nativer Codebibliotheken führt zu erheblichen Größeneinsparungen. Aufgrund der fehlenden Informationen wie Klassen- und Funktionsnamen ist es jedoch nicht möglich, Abstürze in der Google Play Console zu diagnostizieren.

Native Unterstützung für Abstürze

In der Google Play Console werden native Abstürze unter Android Vitals erfasst. In wenigen Schritten kannst du eine Datei mit Symbolen zum Debuggen von nativem Code für deine App generieren und hochladen. Diese Datei aktiviert symbolisch dargestellte native Absturz-Stacktraces (einschließlich Klassen- und Funktionsnamen) in Android Vitals, die dir beim Debuggen deiner App in der Produktionsumgebung helfen. Diese Schritte variieren je nach Version des in Ihrem Projekt verwendeten Android-Gradle-Plug-ins und der Build-Ausgabe Ihres Projekts.

Android-Gradle-Plug-in ab Version 4.1

Wenn Sie in Ihrem Projekt ein Android App Bundle erstellen, können Sie die native Symboldatei zum Debuggen automatisch hinzufügen. Wenn Sie diese Datei in Release-Builds verwenden möchten, fügen Sie der Datei build.gradle.kts Ihrer App Folgendes hinzu:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Wählen Sie die Symbolebene für die Fehlerbehebung aus:

  • Mit SYMBOL_TABLE kannst du Funktionsnamen in den symbolisch dargestellten Stacktraces der Play Console abrufen. Dieses Level unterstützt Tombstones.
  • Verwende FULL, um Funktionsnamen, Dateien und Zeilennummern in den symbolisch dargestellten Stacktraces der Play Console abzurufen.

Wenn in deinem Projekt ein APK erstellt wird, kannst du die oben beschriebene Build-Einstellung build.gradle.kts verwenden, um die Datei mit den Symbolen zum Debuggen von nativem Code separat zu generieren. Lade die native Debugging-Symboldatei manuell in die Google Play Console hoch. Im Rahmen des Build-Prozesses gibt das Android-Gradle-Plug-in diese Datei am folgenden Projektspeicherort aus:

app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip

Android-Gradle-Plug-in Version 4.0 oder niedriger (und andere Build-Systeme)

Im Rahmen des Build-Prozesses speichert das Android-Gradle-Plug-in eine Kopie der nicht entfernten Bibliotheken in einem Projektverzeichnis. Diese Verzeichnisstruktur sieht etwa so aus:

app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── arm64-v8a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── x86/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
└── x86_64/
    ├── libgameengine.so
    ├── libothercode.so
    └── libvideocodec.so
  1. Komprimieren Sie den Inhalt dieses Verzeichnisses:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Lade die symbols.zip-Datei manuell in die Google Play Console hoch.

Ressourcen verkleinern

Das Verkleinern von Ressourcen funktioniert nur in Verbindung mit der Codeverkleinerung. Nachdem der Codeverkleinerer den gesamten nicht verwendeten Code entfernt hat, kann er ermitteln, welche Ressourcen die Anwendung noch verwendet. Dies gilt insbesondere, wenn Sie Codebibliotheken hinzufügen, die Ressourcen enthalten. Sie müssen nicht verwendeten Bibliothekscode entfernen, damit auf die Bibliotheksressourcen kein Verweis mehr besteht und sie somit vom Ressourcenschrumpfer entfernt werden können.

Um das Verkleinern von Ressourcen zu aktivieren, setzen Sie das Attribut shrinkResources in Ihrem Build-Skript auf true (neben minifyEnabled zum Verkleinern von Code). Beispiel:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Cool

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'
        }
    }
}

Wenn Sie Ihre Anwendung noch nicht mit minifyEnabled zum Verkleinern von Code erstellt haben, versuchen Sie dies, bevor Sie shrinkResources aktivieren. Denn möglicherweise müssen Sie die Datei proguard-rules.pro so bearbeiten, dass dynamisch erstellte oder aufgerufene Klassen oder Methoden beibehalten werden, bevor Sie mit dem Entfernen von Ressourcen beginnen.

Anpassen, welche Ressourcen beibehalten werden sollen

Wenn Sie bestimmte Ressourcen beibehalten oder verwerfen möchten, erstellen Sie in Ihrem Projekt eine XML-Datei mit einem <resources>-Tag. Geben Sie dann jede zu behaltende Ressource im Attribut tools:keep und jede zu verwerfende Ressource im Attribut tools:discard an. Beide Attribute akzeptieren eine durch Kommas getrennte Liste von Ressourcennamen. Sie können das Sternchenzeichen als Platzhalter verwenden.

Beispiel:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

Speichern Sie diese Datei in Ihren Projektressourcen, z. B. unter res/raw/keep.xml. Der Build verpackt diese Datei nicht in Ihre Anwendung.

Die Angabe, welche Ressourcen verworfen werden sollen, mag unsinnig erscheinen, wenn Sie sie stattdessen löschen könnten. Dies kann jedoch bei der Verwendung von Build-Varianten nützlich sein. Sie können beispielsweise alle Ihre Ressourcen im gemeinsamen Projektverzeichnis ablegen und dann eine eigene keep.xml-Datei für jede Build-Variante erstellen, wenn Sie wissen, dass eine bestimmte Ressource im Code verwendet wird (und daher nicht vom Shrinker entfernt wurde), aber Sie wissen, dass sie für die jeweilige Build-Variante nicht verwendet wird. Es ist auch möglich, dass die Build-Tools eine Ressource fälschlicherweise bei Bedarf identifiziert haben. Dies kann daran liegen, dass der Compiler die Ressourcen-IDs inline hinzufügt und dann der Ressourcenanalysator den Unterschied zwischen einer tatsächlich referenzierten Ressource und einem Ganzzahlwert im Code, der denselben Wert hat, möglicherweise nicht kennt.

Strikte Referenzprüfungen aktivieren

Normalerweise kann der Ressourcenverkleinerer genau feststellen, ob eine Ressource verwendet wird. Wenn Ihr Code jedoch Resources.getIdentifier() aufruft (oder eine Ihrer Bibliotheken dies tut – die AppCompat-Bibliothek –), sucht der Code nach Ressourcennamen basierend auf dynamisch generierten Strings. In diesem Fall verhält sich der Ressourcenschrumpf standardmäßig defensiv und markiert alle Ressourcen mit einem übereinstimmenden Namensformat als potenziell verwendet und nicht zum Löschen verfügbar.

Der folgende Code bewirkt beispielsweise, dass alle Ressourcen mit dem Präfix img_ als verwendet markiert werden.

Kotlin

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Java

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

Der Ressourcenschrumpfer durchsucht auch alle Stringkonstanten in Ihrem Code sowie verschiedene res/raw/-Ressourcen und sucht nach Ressourcen-URLs in einem Format, das file:///android_res/drawable//ic_plus_anim_016.png ähnelt. Wenn solche Strings gefunden werden, werden sie nicht entfernt, wenn sie aussehen, als könnten sie zum Erstellen solcher URLs verwendet werden.

Dies sind Beispiele für den sicheren Verkleinerungsmodus, der standardmäßig aktiviert ist. Sie können diese Einstellung jedoch deaktivieren und festlegen, dass der Ressourcenverkleinerer nur Ressourcen beibehält, die mit Sicherheit verwendet werden. Setzen Sie dazu shrinkMode in der Datei keep.xml auf strict:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

Wenn Sie den strikten Verkleinerungsmodus aktivieren und Ihr Code wie oben gezeigt auf Ressourcen mit dynamisch generierten Strings verweist, müssen Sie diese Ressourcen manuell mit dem Attribut tools:keep beibehalten.

Nicht verwendete alternative Ressourcen entfernen

Der Gradle-Ressourcenverkleinerer entfernt nur Ressourcen, auf die in Ihrem App-Code nicht verwiesen wird. Das bedeutet, dass keine alternativen Ressourcen für unterschiedliche Gerätekonfigurationen entfernt werden. Bei Bedarf können Sie das Attribut resConfigs des Android-Gradle-Plug-ins verwenden, um alternative Ressourcendateien zu entfernen, die Ihre App nicht benötigt.

Wenn Sie beispielsweise eine Bibliothek mit Sprachressourcen wie AppCompat oder Google Play-Diensten verwenden, enthält Ihre App alle übersetzten Sprachstrings für die Nachrichten in diesen Bibliotheken, unabhängig davon, ob der Rest Ihrer App in dieselben Sprachen übersetzt ist oder nicht. Wenn Sie nur die Sprachen beibehalten möchten, die Ihre App offiziell unterstützt, können Sie diese mit dem Attribut resConfig angeben. Alle Ressourcen für nicht angegebene Sprachen werden entfernt.

Das folgende Snippet zeigt, wie Sie Ihre Sprachressourcen auf Englisch und Französisch beschränken:

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

Cool

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

Wenn eine App im Android App Bundle-Format veröffentlicht wird, werden beim Installieren der App standardmäßig nur die Sprachen heruntergeladen, die auf dem Gerät des Nutzers konfiguriert sind. Außerdem werden nur Ressourcen im Download berücksichtigt, die der Bildschirmdichte des Geräts entsprechen, und native Bibliotheken, die dem ABI des Geräts entsprechen. Weitere Informationen findest du unter Android App Bundle-Konfiguration.

Bei Legacy-Apps, die mit APKs veröffentlicht werden, die vor August 2021 erstellt wurden, kannst du festlegen, welche Bildschirmdichte oder ABI-Ressourcen in deinem APK enthalten sein sollen. Dazu musst du mehrere APKs erstellen, die jeweils auf eine andere Gerätekonfiguration ausgerichtet sind.

Doppelte Ressourcen zusammenführen

Standardmäßig führt Gradle auch identisch benannte Ressourcen zusammen, z. B. Drawables mit demselben Namen, die sich in verschiedenen Ressourcenordnern befinden können. Dieses Verhalten wird nicht vom Attribut shrinkResources gesteuert und kann nicht deaktiviert werden, da es erforderlich ist, um Fehler zu vermeiden, wenn mehrere Ressourcen mit dem Namen übereinstimmen, nach dem Ihr Code sucht.

Ressourcen werden nur zusammengeführt, wenn zwei oder mehr Dateien denselben Ressourcennamen, -typ und -qualifizierer haben. Gradle wählt die Datei aus, die gemäß der unten beschriebenen Prioritätsreihenfolge die beste Wahl unter den Duplikaten ist, und übergibt nur diese eine Ressource zur Verteilung im endgültigen Artefakt an die AAPT.

Gradle sucht an den folgenden Speicherorten nach doppelten Ressourcen:

  • Die Hauptressourcen, die dem Hauptquellensatz zugeordnet sind und sich in der Regel in src/main/res/ befinden.
  • Die Varianten-Overlays vom Build-Typ und den Build-Geschmacksrichtungen.
  • Die Abhängigkeiten des Bibliotheksprojekts.

Gradle führt doppelte Ressourcen in der folgenden kaskadierenden Prioritätsreihenfolge zusammen:

Abhängigkeiten → Hauptseite → Build-Flavor → Build-Typ

Wenn beispielsweise eine doppelte Ressource sowohl in Ihren Hauptressourcen als auch in einem Build-Flavor angezeigt wird, wählt Gradle die Ressource im Build-Flavor aus.

Wenn identische Ressourcen im selben Quellsatz vorkommen, kann Gradle sie nicht zusammenführen. Es wird ein Fehler beim Zusammenführen von Ressourcen ausgegeben. Das kann passieren, wenn Sie im Attribut sourceSet der Datei build.gradle.kts mehrere Quellsätze definieren, z. B. wenn sowohl src/main/res/ als auch src/main/res2/ identische Ressourcen enthalten.

Code verschleiern

Der Zweck der Verschleierung besteht darin, die App-Größe zu reduzieren, indem Sie die Namen der Klassen, Methoden und Felder Ihrer App kürzen. Das folgende Beispiel ist ein Beispiel für die Verschleierung mit R8:

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
    android.content.Context mContext -> a
    int mListItemLayout -> O
    int mViewSpacingRight -> l
    android.widget.Button mButtonNeutral -> w
    int mMultiChoiceItemLayout -> M
    boolean mShowTitle -> P
    int mViewSpacingLeft -> j
    int mButtonPanelSideLayout -> K

Durch die Verschleierung wird zwar kein Code aus Ihrer Anwendung entfernt, aber in Anwendungen mit DEX-Dateien, die viele Klassen, Methoden und Felder indexieren, ergeben sich erhebliche Größeneinsparungen. Da bei der Verschleierung jedoch andere Teile des Codes umbenannt werden, sind für bestimmte Aufgaben, z. B. die Prüfung von Stacktraces, zusätzliche Tools erforderlich. Weitere Informationen zum Stacktrace nach der Verschleierung finden Sie im Abschnitt zum Decodieren eines verschleierten Stacktrace.

Wenn Ihr Code außerdem eine vorhersehbare Benennung für die Methoden und Klassen Ihrer Anwendung erfordert, sollten Sie diese Signaturen beispielsweise bei Verwendung der Reflexion als Einstiegspunkte behandeln und Regeln dafür festlegen, wie im Abschnitt zum Anpassen des zu behaltenden Codes beschrieben. Diese Beibehaltungsregeln weisen R8 an, diesen Code nicht nur im endgültigen DEX Ihrer Anwendung beizubehalten, sondern auch seine ursprüngliche Bezeichnung beizubehalten.

Verschleierten Stacktrace decodieren

Nachdem R8 Ihren Code verschleiert hat, ist es schwierig (wenn nicht unmöglich, einen Stacktrace zu verstehen), da die Namen von Klassen und Methoden möglicherweise geändert wurden. Um den ursprünglichen Stacktrace zu erhalten, sollten Sie den Stacktrace erneut verfolgen.

Codeoptimierung

Um Ihre App noch weiter zu optimieren, prüft R8 Ihren Code auf einer tieferen Ebene, um nicht verwendeten Code zu entfernen oder nach Möglichkeit neu zu schreiben, sodass er weniger ausführlich ist. Im Folgenden finden Sie einige Beispiele für solche Optimierungen:

  • Wenn Ihr Code den else {}-Zweig für eine bestimmte if/else-Anweisung nie annimmt, entfernt R8 möglicherweise den Code für den else {}-Zweig.
  • Wenn Ihr Code eine Methode nur an wenigen Stellen aufruft, wird die Methode in R8 möglicherweise entfernt und bei einigen wenigen Aufrufwebsites inline eingefügt.
  • Wenn R8 feststellt, dass eine Klasse nur eine eindeutige Unterklasse hat, und die Klasse selbst nicht instanziiert wird (z. B. eine abstrakte Basisklasse, die nur von einer konkreten Implementierungsklasse verwendet wird), kann R8 die beiden Klassen kombinieren und eine Klasse aus der Anwendung entfernen.
  • Weitere Informationen finden Sie in den Blogposts zur R8-Optimierung von Jake Wharton.

Mit R8 können Sie keine diskreten Optimierungen deaktivieren oder aktivieren oder das Verhalten einer Optimierung ändern. Tatsächlich ignoriert R8 alle ProGuard-Regeln, mit denen versucht wird, Standardoptimierungen wie -optimizations und -optimizationpasses zu ändern. Diese Einschränkung ist wichtig, da das Android Studio-Team bei der fortlaufenden Verbesserung von R8 durch die Aufrechterhaltung eines Standardverhaltens für Optimierungen das Problem leichter beheben kann.

Wenn Sie die Optimierung aktivieren, werden die Stacktraces für Ihre Anwendung geändert. Durch das Inline-Format werden beispielsweise Stapelframes entfernt. Wie Sie die ursprünglichen Stacktraces abrufen, erfahren Sie im Abschnitt zum Zurückziehen.

Auswirkungen auf die Laufzeitleistung

Wenn Verkleinerung, Verschleierung und Optimierung aktiviert sind, verbessert R8 die Laufzeitleistung des Codes (einschließlich Start- und Framezeit im UI-Thread) um bis zu 30%. Wenn Sie eine dieser Optionen deaktivieren, werden die von R8 verwendeten Optimierungen erheblich eingeschränkt.

Wenn R8 aktiviert ist, sollten Sie auch Start-up-Profile erstellen, um eine noch bessere Startleistung zu erzielen.

Strengere Optimierungen aktivieren

R8 enthält eine Reihe zusätzlicher Optimierungen (als "vollständiger Modus" bezeichnet), durch die es sich von ProGuard unterscheidet. Diese Optimierungen sind seit der Android-Gradle-Plug-in-Version 8.0.0 standardmäßig aktiviert.

Sie können diese zusätzlichen Optimierungen deaktivieren, indem Sie Folgendes in die Datei gradle.properties Ihres Projekts aufnehmen:

android.enableR8.fullMode=false

Da sich R8 durch die zusätzlichen Optimierungen vom Verhalten von ProGuard unterscheidet, müssen Sie möglicherweise zusätzliche ProGuard-Regeln hinzufügen, um Laufzeitprobleme zu vermeiden, wenn Sie für ProGuard entwickelte Regeln verwenden. Angenommen, Ihr Code verweist über die Java Reflection API auf eine Klasse. Wenn der Modus „Vollständig“ nicht verwendet wird, geht R8 davon aus, dass Sie Objekte dieser Klasse zur Laufzeit untersuchen und bearbeiten möchten – auch wenn dies bei Ihrem Code nicht der Fall ist – und behält die Klasse und ihren statischen Initialisierer automatisch bei.

Wenn Sie jedoch den Modus "Vollständiger Modus" verwenden, geht R8 nicht von dieser Annahme aus. Wenn R8 behauptet, dass Ihr Code die Klasse sonst niemals zur Laufzeit verwendet, wird die Klasse aus dem endgültigen DEX Ihrer Anwendung entfernt. Das heißt, wenn Sie die Klasse und ihren statischen Initialisierer beibehalten möchten, müssen Sie dazu eine Keep-Regel in Ihre Regeldatei aufnehmen.

Wenn bei der Verwendung des "Full-Modus" von R8 Probleme auftreten, finden Sie auf der FAQ-Seite zu R8 eine mögliche Lösung. Wenn Sie das Problem nicht beheben können, melden Sie einen Fehler.

Stacktraces werden zurückgeführt

Der von R8 verarbeitete Code wird auf verschiedene Weise geändert, was die Verständlichkeit von Stacktraces erschwert, da die Stacktraces nicht genau dem Quellcode entsprechen. Dies kann bei Änderungen der Zeilennummern der Fall sein, wenn Informationen zur Fehlerbehebung nicht beibehalten werden. Dies kann auf Optimierungen wie Inline- und Outline-Funktionen zurückzuführen sein. Den größten Beitrag leistet die Verschleierung, bei der sogar die Namen der Klassen und Methoden geändert werden.

Zur Wiederherstellung des ursprünglichen Stacktrace stellt R8 das retrace-Befehlszeilentool bereit, das im Paket mit Befehlszeilentools enthalten ist.

Damit das erneute Abrufen der Stacktraces Ihrer Anwendung möglich ist, sollten Sie dafür sorgen, dass der Build genügend Informationen für das Trace behält. Fügen Sie dazu der Datei proguard-rules.pro Ihres Moduls die folgenden Regeln hinzu:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

Das Attribut LineNumberTable behält Positionsinformationen in Methoden bei, mit denen diese Positionen in Stacktraces gedruckt werden. Das Attribut SourceFile sorgt dafür, dass alle potenziellen Laufzeiten die Positionsinformationen tatsächlich ausgeben. Die Anweisung -renamesourcefileattribute legt den Namen der Quelldatei in Stacktraces auf SourceFile fest. Der tatsächliche Name der ursprünglichen Quelldatei ist beim Zurückziehen nicht erforderlich, da die Zuordnungsdatei die ursprüngliche Quelldatei enthält.

R8 erstellt bei jeder Ausführung eine mapping.txt-Datei mit den Informationen, die für die Zuordnung von Stacktraces zu den ursprünglichen Stacktraces erforderlich sind. Android Studio speichert die Datei im Verzeichnis <module-name>/build/outputs/mapping/<build-type>/.

Wenn du deine App bei Google Play veröffentlichst, kannst du die Datei mapping.txt für jede Version deiner App hochladen. Bei der Veröffentlichung mit Android App Bundles ist diese Datei automatisch Teil des App Bundle-Inhalts. Google Play verfolgt dann eingehende Stacktraces von Nutzern, die Probleme gemeldet haben, damit du sie in der Play Console überprüfen kannst. Weitere Informationen finden Sie im Hilfeartikel zur Offenlegung von Absturz-Stacktraces.

Fehlerbehebung mit R8

In diesem Abschnitt werden einige Strategien zur Behebung von Problemen beim Aktivieren der Verkleinerung, Verschleierung und Optimierung mit R8 beschrieben. Wenn Sie unten keine Lösung für Ihr Problem finden, lesen Sie auch die FAQ-Seite zu R8 und die Anleitung zur Fehlerbehebung von ProGuard.

Bericht zu entferntem (oder beibehaltenem) Code erstellen

Für die Behebung bestimmter R8-Probleme kann es hilfreich sein, einen Bericht über den gesamten Code aufzurufen, den R8 aus Ihrer Anwendung entfernt hat. Fügen Sie für jedes Modul, für das Sie diesen Bericht erstellen möchten, Ihrer Datei mit benutzerdefinierten Regeln -printusage <output-dir>/usage.txt hinzu. Wenn Sie R8 aktivieren und Ihre Anwendung erstellen, gibt R8 einen Bericht mit dem von Ihnen angegebenen Pfad und Dateinamen aus. Der Bericht über entfernten Code sieht etwa so aus:

androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
    public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
    public boolean hasWindowFeature(int)
    public void setHandleNativeActionModesEnabled(boolean)
    android.view.ViewGroup getSubDecor()
    public void setLocalNightMode(int)
    final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
    public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
    private static final boolean DEBUG
    private static final java.lang.String KEY_LOCAL_NIGHT_MODE
    static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...

Wenn Sie stattdessen einen Bericht der Einstiegspunkte sehen möchten, die R8 aus den Keep-Regeln Ihres Projekts ermittelt, fügen Sie -printseeds <output-dir>/seeds.txt in Ihre benutzerdefinierte Regeldatei ein. Wenn Sie R8 aktivieren und Ihre Anwendung erstellen, gibt R8 einen Bericht mit dem von Ihnen angegebenen Pfad und Dateinamen aus. Der Bericht über beibehaltene Einstiegspunkte sieht in etwa so aus:

com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...

Fehler beim Verkleinern von Ressourcen beheben

Wenn Sie Ressourcen verkleinern, wird im Fenster Build eine Zusammenfassung der Ressourcen angezeigt, die aus der Anwendung entfernt werden. Sie müssen zuerst links im Fenster auf Toggle view klicken, um eine detaillierte Textausgabe von Gradle anzuzeigen. Beispiel:

:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle erstellt außerdem eine Diagnosedatei mit dem Namen resources.txt in <module-name>/build/outputs/mapping/release/ (demselben Ordner wie die Ausgabedateien von ProGuard). Diese Datei enthält Details darüber, welche Ressourcen auf andere Ressourcen verweisen und welche Ressourcen verwendet oder entfernt werden.

Wenn Sie beispielsweise herausfinden möchten, warum @drawable/ic_plus_anim_016 noch in Ihrer Anwendung vorhanden ist, öffnen Sie die Datei resources.txt und suchen Sie nach diesem Dateinamen. So stellen Sie möglicherweise fest, dass von einer anderen Ressource darauf verwiesen wird:

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     @drawable/ic_plus_anim_016

Sie müssen jetzt wissen, warum @drawable/add_schedule_fab_icon_anim erreichbar ist. Wenn Sie eine höhere Suche ausführen, wird diese Ressource unter „Die erreichbaren Root-Ressourcen sind:“ aufgeführt. Dies bedeutet, dass es eine Codereferenz zu add_schedule_fab_icon_anim gibt (d. h., die R.drawable-ID wurde im erreichbaren Code gefunden).

Wenn Sie keine strikte Prüfung verwenden, können Ressourcen-IDs als erreichbar markiert werden, wenn Stringkonstanten vorhanden sind, die so aussehen, als würden sie zum Erstellen von Ressourcennamen für dynamisch geladene Ressourcen verwendet werden. Wenn Sie in diesem Fall die Build-Ausgabe nach dem Ressourcennamen durchsuchen, erhalten Sie möglicherweise eine Meldung wie die folgende:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because it format-string matches string pool constant ic_plus_anim_%1$d.

Wenn Sie einen dieser Strings sehen und sicher sind, dass der String nicht zum dynamischen Laden der angegebenen Ressource verwendet wird, können Sie das Build-System mit dem Attribut tools:discard anweisen, den String zu entfernen, wie im Abschnitt zum Anpassen, welche Ressourcen beibehalten werden beschrieben.