App verkleinern, verschleiern und optimieren

Damit deine App so klein und schnell wie möglich ist, solltest du deinen Release-Build mit isMinifyEnabled = true optimieren und komprimieren.

Das führt zum Verkleinern, wodurch nicht verwendeter Code und nicht verwendete Ressourcen entfernt werden, die Verschleierung, durch die die Namen der Klassen und Mitglieder Ihrer App gekürzt werden, sowie die Optimierung, die aggressivere Strategien zur weiteren Reduzierung der Größe und zur Verbesserung der Leistung Ihrer Anwendung anwendet. Auf dieser Seite wird beschrieben, wie R8 diese Aufgaben zur Kompilierungszeit für Ihr Projekt ausführt und wie Sie sie anpassen können.

Wenn Sie Ihr Projekt mit dem Android Gradle-Plug-in 3.4.0 oder höher erstellen, verwendet das Plug-in ProGuard nicht mehr zur Optimierung der Kompilierungszeit. Stattdessen arbeitet das Plug-in mit dem R8-Compiler zusammen, um die folgenden Kompilierungsaufgaben auszuführen:

  • Code-Verkleinerung (oder Baum-Shaking): Hiermit werden nicht verwendete Klassen, Felder, Methoden und Attribute aus der Anwendung und den Bibliotheksabhängigkeiten erkannt und sicher entfernt. Dies ist ein nützliches Tool zum Umgehen des Referenzlimits von 64.000. Wenn Sie beispielsweise nur wenige APIs einer Bibliotheksabhängigkeit verwenden, kann durch die Verkleinerung der von Ihrer Anwendung nicht verwendete Bibliothekscode identifiziert und nur dieser Code aus der Anwendung entfernt werden. Weitere Informationen finden Sie im Abschnitt zum Verkleinern des Codes.
  • Ressourcenverkleinerung: entfernt nicht verwendete Ressourcen aus der gepackten Anwendung, einschließlich nicht verwendeter Ressourcen in den Bibliotheksabhängigkeiten der Anwendung. Sie funktioniert in Verbindung mit der Codereduzierung, sodass nach dem Entfernen nicht verwendeter Code auch alle Ressourcen, auf die nicht mehr verwiesen wird, sicher entfernt werden können. Weitere Informationen finden Sie im Abschnitt zum Verkleinern Ihrer Ressourcen.
  • Optimierung: untersucht und schreibt Ihren Code, um die Laufzeitleistung zu verbessern und die Größe der DEX-Dateien Ihrer Anwendung weiter zu reduzieren. Dies verbessert die Laufzeitleistung von Code um bis zu 30 % und verbessert das Start- und Frame-Timing drastisch. Wenn R8 beispielsweise erkennt, dass der Zweig else {} für eine bestimmte if/else-Anweisung nie verwendet wird, entfernt R8 den Code für den Zweig else {}. 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 geringeren DEX-Dateigröße führt. Weitere Informationen finden Sie im Abschnitt zum Verschleieren des Codes.

Beim Erstellen der Release-Version Ihrer App kann R8 so konfiguriert werden, dass die oben beschriebenen Aufgaben zur Kompilierungszeit für Sie ausgeführt werden. Über ProGuard-Regeldateien können Sie außerdem bestimmte Aufgaben deaktivieren oder das Verhalten von R8 anpassen. R8 funktioniert mit allen Ihren vorhandenen ProGuard-Regeldateien. Wenn Sie das Android-Gradle-Plug-in zur Verwendung von R8 aktualisieren, sollten Sie Ihre vorhandenen Regeln also nicht ändern müssen.

Verkleinern, 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 Standardcompiler, der den Java-Bytecode Ihres Projekts in das DEX-Format konvertiert, das auf der Android-Plattform ausgeführt wird. Wenn Sie jedoch ein neues Projekt mit Android Studio erstellen, sind Verkleinern, Verschleierung und Codeoptimierung standardmäßig nicht aktiviert. Das liegt daran, dass diese Optimierungen während der Kompilierung die Build-Dauer Ihres Projekts erhöhen und zu Fehlern führen können, wenn Sie den zu behaltenden Code nicht ausreichend anpassen.

Daher ist es am besten, diese Compile-Aufgaben zu aktivieren, wenn Sie die endgültige Version Ihrer App erstellen, die Sie vor der Veröffentlichung testen. Fügen Sie Folgendes in Ihr Build-Script auf Projektebene ein, um Verkleinerung, Verschleierung und Optimierung zu aktivieren.

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"
            )
        }
    }
    ...
}

Groovig

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 sein Standardverhalten zu ändern und die Struktur Ihrer App besser zu verstehen, z. B. die Klassen, die als Einstiegspunkte in den Code Ihrer App dienen. Sie können zwar einige dieser Regeldateien ändern, einige werden jedoch möglicherweise automatisch von Tools zur Kompilierungszeit wie AAPT2 generiert oder von den Bibliotheksabhängigkeiten Ihrer Anwendung übernommen. In der folgenden Tabelle werden die Quellen der ProGuard-Regeldateien beschrieben, die von R8 verwendet werden.

Quelle Standort Beschreibung
Android Studio <module-dir>/proguard-rules.pro Wenn Sie mit Android Studio ein neues Modul 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 wie Ihre benutzerdefinierten Aufbewahrungsregeln ein.

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

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

Hinweis:Das Android-Gradle-Plug-in enthält zusätzliche vordefinierte ProGuard-Regeldateien. Es wird jedoch empfohlen, proguard-android-optimize.txt zu verwenden.

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

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

Wenn eine AAR-Bibliothek mit ihrer eigenen ProGuard-Regeldatei veröffentlicht wird und Sie diese AAR als Kompilierungszeitabhängigkeit hinzufügen, wendet R8 die Regeln automatisch beim Kompilieren des Projekts an.

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

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

Android Asset Package Tool 2 (AAPT2) Nachdem Sie das Projekt mit minifyEnabled true erstellt haben: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 generiert Keep-Regeln, die auf Verweisen auf Klassen im Manifest, in Layouts und in anderen App-Ressourcen Ihrer App basieren. AAPT2 enthält beispielsweise eine Keep-Regel 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, damit Sie Ihre eigenen Regeln hinzufügen können. Sie können zusätzliche Konfigurationen hinzufügen, die von R8 zur Kompilierungszeit angewendet werden.

Wenn Sie das Attribut minifyEnabled auf true setzen, kombiniert R8 Regeln aus allen oben aufgeführten verfügbaren Quellen. Das sollten Sie bei der Fehlerbehebung mit R8 beachten, da andere Abhängigkeiten zur Kompilierung (z. B. Bibliotheksabhängigkeiten) Änderungen am R8-Verhalten zur Folge haben können, die Sie nicht kennen.

Wenn Sie einen vollständigen Bericht mit allen Regeln ausgeben möchten, die R8 beim Erstellen Ihres Projekts anwendet, fügen Sie Folgendes in die proguard-rules.pro-Datei Ihres Moduls ein:

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

Zusätzliche Konfigurationen einschließen

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

Sie können beispielsweise für jede Build-Variante spezifische Regeln hinzufügen, indem Sie ein weiteres proguardFiles-Attribut im entsprechenden productFlavor-Block hinzufügen. Mit der folgenden Gradle-Datei wird flavor2-rules.pro zur Produktvariante flavor2 hinzugefügt. Jetzt verwendet flavor2 alle drei ProGuard-Regeln, da auch die aus dem Block release angewendet werden.

Außerdem kannst du das Attribut testProguardFiles hinzufügen, das eine Liste der 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")
        }
    }
}

Groovig

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 des Codes mit R8 ist standardmäßig aktiviert, wenn Sie das Attribut minifyEnabled auf true setzen.

Codeverkleinerung (auch als Baumwackeln bezeichnet) ist der Prozess des Entfernens von Code, der von R8 als zur Laufzeit nicht benötigt wird. Dieser Prozess 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 bestimmt R8 zuerst alle Einstiegspunkte in den Code Ihrer Anwendung anhand des kombinierten Satzes von Konfigurationsdateien. Zu diesen Einstiegspunkten gehören alle Klassen, mit denen die Android-Plattform die Aktivitäten oder Dienste Ihrer App öffnen kann. Ausgehend von jedem Einstiegspunkt prüft R8 den Code der Anwendung, um ein Diagramm mit allen Methoden, Mitgliedsvariablen und anderen Klassen zu erstellen, auf die Ihre Anwendung zur Laufzeit zugreifen könnte. Code, der nicht mit diesem Diagramm verbunden ist, wird als nicht erreichbar eingestuft und möglicherweise aus der Anwendung entfernt.

Abbildung 1 zeigt eine Anwendung mit einer Laufzeitbibliotheksabhängigkeit. Bei der Prüfung des Anwendungscodes stellt R8 fest, dass die Methoden foo(), faz() und bar() vom Einstiegspunkt MainActivity.class aus erreichbar sind. Die Klasse OkayApi.class oder die zugehörige Methode baz() wird jedoch nie von Ihrer Anwendung zur Laufzeit verwendet und R8 entfernt diesen Code beim Verkleinern der Anwendung.

Abbildung 1: Zur Kompilierung erstellt R8 ein Diagramm, das auf den kombinierten Keep-Regeln Ihres Projekts basiert, um nicht erreichbaren Code zu ermitteln.

R8 bestimmt Einstiegspunkte über -keep-Regeln in den R8-Konfigurationsdateien des Projekts. Das heißt, Regeln geben Klassen an, die R8 beim Verkleinern Ihrer App nicht verwerfen soll, während R8 diese Klassen als mögliche Einstiegspunkte in Ihre App betrachtet. Das Android-Gradle-Plug-in und AAPT2 generieren automatisch Aufbewahrungsregeln, 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 Aufbewahrungsregeln anpassen möchten, lesen Sie den Abschnitt zum Anpassen des zu behaltenden Codes.

Wenn Sie stattdessen nur die Größe der Ressourcen Ihrer Anwendung reduzieren möchten, fahren Sie mit dem Abschnitt zum Verkleinern Ihrer Ressourcen fort.

Anpassen, welcher Code beibehalten werden soll

In den meisten Fällen reicht die standardmäßige ProGuard-Regeldatei (proguard-android- optimize.txt) aus, damit R8 nur den nicht verwendeten Code entfernt. Manche Situationen sind für R8 jedoch schwierig zu analysieren und es wird möglicherweise Code entfernt, den Ihre Anwendung tatsächlich benötigt. Hier einige Beispiele für Fälle, in denen Code fälschlicherweise entfernt wird:

  • Wenn Ihre App eine Methode von der Java Native Interface (JNI) aufruft
  • Wenn Ihre App zur Laufzeit Code abruft (z. B. bei einer Reflexion)

Beim Testen Ihrer Anwendung sollten Fehler ermittelt werden, die durch unrechtmäßig entfernten Code verursacht wurden. Sie können aber auch prüfen, welcher Code entfernt wurde, indem Sie einen Bericht über entfernten Code erstellen.

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

-keep public class MyClass

Alternativ können Sie dem Code, den Sie beibehalten möchten, die Annotation @Keep hinzufügen. Wenn Sie @Keep für eine Klasse hinzufügen, bleibt die gesamte Klasse unverändert. Wenn Sie sie für eine Methode oder ein Feld hinzufügen, bleiben die Methode bzw. das Feld und der zugehörige Name sowie der Klassenname erhalten. Diese Annotation ist nur verfügbar, wenn Sie die AndroidX-Anmerkungsbibliothek verwenden und die ProGuard-Regeldatei einbinden, die im Paket mit dem Android-Gradle-Plug-in enthalten ist, wie im Abschnitt zum Aktivieren der Verkleinerung beschrieben.

Bei der Verwendung der Option -keep sollten Sie viele Aspekte beachten. Weitere Informationen zum Anpassen Ihrer Regeldatei finden Sie im ProGuard-Handbuch. Im Abschnitt Fehlerbehebung werden weitere häufige Probleme beschrieben, die beim Entfernen Ihres Codes auftreten können.

Native Bibliotheken entfernen

Standardmäßig werden native Codebibliotheken in Release-Builds Ihrer App entfernt. Dabei werden die Symboltabelle und Debugging-Informationen in allen nativen Bibliotheken entfernt, 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 angezeigt. In wenigen Schritten können Sie eine Datei mit Symbolen zum Debuggen von nativem Code für Ihre App generieren und hochladen. Sie ermöglicht symbolisch dargestellte native Absturz-Stacktraces (einschließlich Klassen- und Funktionsnamen) in Android Vitals, um Fehler in Ihrer App in der Produktion zu beheben. Diese Schritte variieren je nach Version des Android-Gradle-Plug-ins, das in Ihrem Projekt verwendet wird, und von der Build-Ausgabe Ihres Projekts.

Android-Gradle-Plug-in ab Version 4.1

Wenn Ihr Projekt ein Android App Bundle erstellt, können Sie die native Debug-Symboldatei automatisch darin aufnehmen. Wenn Sie diese Datei in Release-Builds aufnehmen möchten, fügen Sie Folgendes in die Datei build.gradle.kts Ihrer App ein:

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

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

  • Verwenden Sie SYMBOL_TABLE, um Funktionsnamen in den symbolisch dargestellten Stacktraces der Play Console abzurufen. Auf dieser Stufe werden Tombstones unterstützt.
  • Verwenden Sie FULL, um Funktionsnamen, Dateien und Zeilennummern in den symbolisch dargestellten Stacktraces der Play Console abzurufen.

Wenn Ihr Projekt ein APK erstellt, verwenden Sie die oben gezeigte Build-Einstellung build.gradle.kts, um die native Debug-Symboldatei separat zu generieren. Laden Sie die Symboldatei zum Debuggen von nativem Code 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 ab Version 4.0 (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 in 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. Laden Sie die Datei symbols.zip manuell in die Google Play Console hoch.

Ressourcen verkleinern

Das Ressourcenverkleinern funktioniert nur in Verbindung mit dem Codeverkleinern. Nachdem der Code-Stricker 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 die Bibliotheksressourcen nicht referenziert und daher durch den Ressourcenschrumpf entfernt werden können.

Zum Aktivieren der Ressourcenverkleinerung setzen Sie das Attribut shrinkResources im Build-Skript auf true (zusammen mit minifyEnabled für die Codekomprimierung). Beispiel:

Kotlin

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

Groovig

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

Wenn Sie Ihre Anwendung noch nicht mit minifyEnabled zum Erstellen des Codes erstellt haben, versuchen Sie dies, bevor Sie shrinkResources aktivieren. Möglicherweise müssen Sie die Datei proguard-rules.pro bearbeiten, um dynamisch erstellte oder aufgerufene Klassen oder Methoden beizubehalten, bevor Sie mit dem Entfernen von Ressourcen beginnen.

Festlegen, welche Ressourcen behalten 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 dabei jede Ressource, die Sie behalten möchten, im Attribut tools:keep und jede Ressource, die verworfen werden soll, im Attribut tools:discard an. Beide Attribute akzeptieren eine durch Kommas getrennte Liste von Ressourcennamen. Sie können das Sternchen 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. Diese Datei wird vom Build nicht in Ihre Anwendung verpackt.

Angeben, welche Ressourcen zu verwerfen sind, mag Ihnen unübersichtlich erscheinen, wenn Sie sie stattdessen löschen könnten. Dies kann jedoch bei Verwendung von Build-Varianten nützlich sein. Sie können beispielsweise alle Ressourcen im gemeinsamen Projektverzeichnis ablegen und dann für jede Build-Variante eine andere keep.xml-Datei erstellen, wenn Sie wissen, dass eine bestimmte Ressource im Code verwendet wird (und daher nicht von einem Sharin entfernt wird), Sie aber wissen, dass sie für die angegebene Build-Variante nicht verwendet wird. Es ist auch möglich, dass die Build-Tools eine Ressource fälschlicherweise als erforderlich identifiziert haben. Dies ist möglich, da der Compiler die Ressourcen-IDs inline hinzufügt und dann der Resource Analyzer den Unterschied zwischen einer tatsächlich referenzierten Ressource und einem Ganzzahlwert im Code, die denselben Wert haben, möglicherweise nicht kennt.

Strikte Referenzprüfungen aktivieren

Normalerweise kann der Ressourcenschrumpf genau bestimmen, ob eine Ressource verwendet wird. Wenn Ihr Code jedoch Resources.getIdentifier() aufruft (oder eine Ihrer Bibliotheken dies tut – in der AppCompat-Bibliothek), sucht Ihr Code anhand von dynamisch generierten Strings nach Ressourcennamen. In diesem Fall verhält sich der Ressourcenverkleinerer standardmäßig defensiv und markiert alle Ressourcen mit einem übereinstimmenden Namensformat als potenziell verwendet und kann nicht entfernt werden.

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 Ressourcenschrumpf prüft auch alle Stringkonstanten in Ihrem Code sowie verschiedene res/raw/-Ressourcen und sucht nach Ressourcen-URLs in einem Format wie file:///android_res/drawable//ic_plus_anim_016.png. Wenn es Strings wie diesen oder andere findet, die so aussehen, als könnten sie zum Erstellen solcher URLs verwendet werden, werden sie nicht entfernt.

Dies sind Beispiele für den sicheren Modus, der standardmäßig aktiviert ist. Sie können diese Option jedoch deaktivieren und angeben, dass der Ressourcenverkürzung nur Ressourcen behält, deren Verwendung nachweislich sicher ist. Dazu setzen Sie 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 auch auf Ressourcen mit dynamisch generierten Strings verweist, wie oben gezeigt, müssen Sie diese Ressourcen manuell mit dem Attribut tools:keep beibehalten.

Nicht verwendete alternative Ressourcen entfernen

Mit dem Gradle-Ressourcen-Srinker werden nur Ressourcen entfernt, auf die im App-Code nicht verwiesen wird. Dadurch werden keine alternativen Ressourcen für verschiedene Gerätekonfigurationen entfernt. Bei Bedarf können Sie mit dem Attribut resConfigs des Android-Gradle-Plug-ins alternative Ressourcendateien entfernen, die Ihre App nicht benötigt.

Wenn Sie beispielsweise eine Bibliothek verwenden, die Sprachressourcen wie AppCompat oder Google Play-Dienste enthält, enthält Ihre App alle übersetzten Sprachstrings für die Nachrichten in diesen Bibliotheken, unabhängig davon, ob der Rest Ihrer App in die gleichen Sprachen übersetzt wird oder nicht. Wenn Sie nur die Sprachen beibehalten möchten, die Ihre App offiziell unterstützt, können Sie diese Sprachen über das 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 können:

Kotlin

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

Groovig

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

Bei der Veröffentlichung einer App mit dem Android App Bundle-Format werden bei der Installation der App standardmäßig nur die auf dem Gerät eines Nutzers konfigurierten Sprachen heruntergeladen. Ebenso werden nur Ressourcen, die der Bildschirmdichte des Geräts entsprechen, im Download enthalten. Außerdem sind native Bibliotheken, die dem ABI des Geräts entsprechen, enthalten. Weitere Informationen finden Sie unter Konfiguration von Android App Bundles.

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

Doppelte Ressourcen zusammenführen

Standardmäßig führt Gradle auch Ressourcen mit identischem Namen zusammen, z. B. Drawables mit demselben Namen, die sich möglicherweise in verschiedenen Ressourcenordnern befinden. Dieses Verhalten wird nicht durch das Attribut shrinkResources gesteuert und kann nicht deaktiviert werden, da Fehler vermieden werden müssen, wenn mehrere Ressourcen mit dem Namen übereinstimmen, nach dem Ihr Code sucht.

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

Gradle sucht an den folgenden Speicherorten nach doppelten Ressourcen:

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

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

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

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

Wenn identische Ressourcen im selben Quellsatz enthalten sind, kann Gradle sie nicht zusammenführen und gibt einen Fehler bei der Zusammenführung von Ressourcen aus. 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 verringern, indem die Namen der Klassen, Methoden und Felder Ihrer App gekürzt werden. Hier 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, bei Anwendungen mit DEX-Dateien, die viele Klassen, Methoden und Felder indexieren, lassen sich jedoch erhebliche Größeneinsparungen erzielen. Da bei der Verschleierung jedoch verschiedene Teile Ihres Codes umbenannt werden, erfordern bestimmte Aufgaben, z. B. das Prüfen von Stacktraces, zusätzliche Tools. Informationen zum Stacktrace nach der Verschleierung finden Sie im Abschnitt zum Decodieren eines verschleierten Stacktrace.

Wenn Ihr Code auf einer vorhersehbaren Benennung der Methoden und Klassen Ihrer Anwendung basiert – beispielsweise bei Verwendung von Reflexionen – sollten Sie diese Signaturen als Einstiegspunkte behandeln und Aufbewahrungsregeln für sie festlegen, wie im Abschnitt zum Anpassen des zu behaltenden Codes beschrieben. Diese Keep-Regeln weisen R8 an, diesen Code nicht nur im endgültigen DEX Ihrer App, sondern auch seine ursprüngliche Benennung beizubehalten.

Verschleierten Stacktrace decodieren

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

Codeoptimierung

Um Ihre Anwendung noch weiter zu optimieren, untersucht R8 Ihren Code auf einer tieferen Ebene, um nicht verwendeten Code zu entfernen oder ihn, wenn möglich, neu zu schreiben, um ihn weniger detailliert zu gestalten. Hier einige Beispiele für solche Optimierungen:

  • Wenn Ihr Code den else {}-Zweig nie für eine bestimmte if/else-Anweisung annimmt, entfernt R8 möglicherweise den Code für den else {}-Zweig.
  • Wenn Ihr Code eine Methode nur an wenigen Stellen aufruft, entfernt R8 die Methode möglicherweise und inline an den wenigen Aufrufseiten.
  • 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 Standardoptimierungen wie -optimizations und -optimizationpasses geändert werden sollen. Diese Einschränkung ist wichtig, da das Android Studio-Team bei der Verbesserung von R8 bei der Aufrechterhaltung eines Standardverhaltens dabei hilft, auftretende Probleme einfach zu beheben.

Durch das Aktivieren der Optimierung werden die Stacktraces für Ihre Anwendung geändert. Durch das Inline-Objekt werden beispielsweise Stapelframes entfernt. Wie Sie die ursprünglichen Stacktraces erhalten, erfahren Sie im Abschnitt Zurückziehen.

Auswirkungen auf die Laufzeitleistung

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

Wenn R8 aktiviert ist, sollten Sie außerdem Startprofile erstellen, um eine noch bessere Startleistung zu erzielen.

Strengere Optimierungen ermöglichen

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

Sie können diese zusätzlichen Optimierungen deaktivieren, indem Sie Folgendes in die gradle.properties-Datei 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 einbinden, 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 "Full Mode" nicht verwendet wird, geht R8 davon aus, dass Sie Objekte dieser Klasse zur Laufzeit untersuchen und bearbeiten möchten – auch wenn Ihr Code dies nicht tut. Die Klasse und ihr statischer Initialisierer werden automatisch beibehalten.

Wenn Sie jedoch den „Full Mode“ verwenden, geht R8 nicht von dieser Annahme aus. Wenn R8 behauptet, dass Ihr Code ansonsten nie die Klasse zur Laufzeit verwendet, wird die Klasse aus dem endgültigen DEX Ihrer Anwendung entfernt. Wenn Sie also die Klasse und ihren statischen Initialisierer beibehalten möchten, müssen Sie Ihrer Regeldatei eine Keep-Regel hinzufügen.

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

Stacktraces zurückziehen

Von R8 verarbeiteter Code wird auf verschiedene Weise geändert, wodurch Stacktraces schwerer verständlich sind, da die Stacktraces nicht genau dem Quellcode entsprechen. Dies kann bei Änderungen an den Zeilennummern der Fall sein, wenn die Informationen zur Fehlerbehebung nicht beibehalten werden. Dies kann an Optimierungen wie Inline- und Gliedern liegen. Der größte Mitwirkende ist die Verschleierung, bei der selbst die Klassen und Methoden die Namen ändern.

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

Damit das Zurückziehen der Stacktraces Ihrer Anwendung unterstützt wird, sollten Sie dafür sorgen, dass der Build genügend Informationen für das Retrace enthält. Fügen Sie dazu der Datei proguard-rules.pro des Moduls die folgenden Regeln hinzu:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

Das Attribut LineNumberTable behält Positionsinformationen in Methoden bei, sodass diese Positionen in Stacktraces gedruckt werden. Das Attribut SourceFile sorgt dafür, dass alle potenziellen Laufzeiten die Positionsinformationen auch 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, die die Informationen enthält, die erforderlich sind, um Stacktraces den ursprünglichen Stacktraces zuzuordnen. 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 wird diese Datei automatisch in den Inhalt des App Bundles aufgenommen. Google Play verfolgt dann eingehende Stacktraces von Nutzern zurück, die von Nutzern gemeldet wurden, damit du sie in der Play Console überprüfen kannst. Weitere Informationen finden Sie in dem Hilfeartikel zum Offenlegen 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 Seite mit den R8-FAQs und die Anleitung zur Fehlerbehebung von ProGuard.

Bericht zu entferntem (oder beibehaltenen) Code erstellen

Zur Unterstützung bei der Behebung bestimmter R8-Probleme kann es hilfreich sein, einen Bericht über den gesamten Code zu erhalten, den R8 aus Ihrer Anwendung entfernt hat. Fügen Sie für jedes Modul, für das Sie diesen Bericht generieren möchten, -printusage <output-dir>/usage.txt in die Datei mit benutzerdefinierten Regeln ein. Wenn Sie R8 aktivieren und Ihre Anwendung erstellen, gibt R8 einen Bericht mit dem angegebenen Pfad und Dateinamen aus. Der Bericht zum entfernten Code sieht in 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 Datei mit benutzerdefinierten Regeln ein. Wenn Sie R8 aktivieren und Ihre Anwendung erstellen, gibt R8 einen Bericht mit dem 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 wurden. Sie müssen zuerst auf der linken Seite des Fensters auf Ansicht ein-/ausblenden klicken, um die detaillierte Textausgabe von Gradle aufzurufen. 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/. Dies ist derselbe Ordner wie die Ausgabedateien von ProGuard. Diese Datei enthält Details dazu, 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 App vorhanden ist, öffnen Sie die Datei resources.txt und suchen Sie nach dem Dateinamen. Es kann sein, 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. Bei einer Suche nach oben wird diese Ressource unter „Die Root erreichbaren Ressourcen sind:“ aufgeführt. Dies bedeutet, dass ein Codeverweis auf add_schedule_fab_icon_anim vorhanden ist (d. h., die zugehörige 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 könnten sie zum Erstellen von Ressourcennamen für dynamisch geladene Ressourcen verwendet werden. Wenn Sie in diesem Fall in der Build-Ausgabe nach dem Ressourcennamen suchen, finden Sie möglicherweise eine Meldung wie diese:

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, ihn zu entfernen. Eine Beschreibung hierzu finden Sie im Abschnitt zum Anpassen der zu behaltenden Ressourcen.