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 werden die Komprimierung aktiviert, bei der nicht verwendeter Code entfernt wird, die Verschleierung, bei der die Namen der Klassen und Mitglieder Ihrer App verkürzt werden, und die Optimierung, bei der verbesserte Codeoptimierungsstrategien angewendet werden, um die Größe weiter zu reduzieren und die Leistung Ihrer App zu verbessern. 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 nicht mehr ProGuard für die Codeoptimierung zur Laufzeit. Stattdessen arbeitet das Plug-in mit dem R8-Compiler, um die folgenden Aufgaben zur Kompilierungszeit zu verarbeiten:

  • Code-Komprimierung (oder Tree-Shaking): Hiermit werden nicht verwendete Klassen, Felder, Methoden und Attribute aus Ihrer App und ihren Bibliotheksabhängigkeiten erkannt und sicher entfernt. Dies ist ein nützliches Tool, um das 64k-Referenzlimit zu umgehen. Wenn Sie beispielsweise nur wenige APIs einer Bibliothekabhängigkeit verwenden, kann durch das Schrumpfen Bibliothekcode identifiziert werden, der in Ihrer App nicht verwendet wird, und nur dieser Code aus Ihrer App entfernt werden. Weitere Informationen finden Sie im Abschnitt zum Schrumpfen von Code.
  • Ressourcenverkleinerung:Entfernt nicht verwendete Ressourcen aus der gepackten Anwendung, einschließlich nicht verwendeter Ressourcen aus den Bibliotheksabhängigkeiten der Anwendung. Dies funktioniert in Verbindung mit der Codekomprimierung. Sobald nicht verwendeter Code entfernt wurde, können auch nicht mehr referenzierte Ressourcen sicher entfernt werden. Weitere Informationen finden Sie im Abschnitt Ressourcen verkleinern.
  • Optimierung:Ihr Code wird geprüft und neu geschrieben, um die Laufzeitleistung zu verbessern und die Größe der DEX-Dateien Ihrer App weiter zu reduzieren. Dadurch wird die Laufzeitleistung von Code um bis zu 30 % verbessert und das Starten und Frame-Timing drastisch optimiert. Wenn R8 beispielsweise erkennt, dass der else {}-Zweig für eine bestimmte if-else-Anweisung nie ausgeführt wird, entfernt R8 den Code für den else {}-Zweig. Weitere Informationen finden Sie im Abschnitt zur Codeoptimierung.
  • Obfuscierung (oder Minimierung von IDs): Der Name von Klassen und Mitgliedern wird verkürzt, was zu einer Verringerung der DEX-Dateigröße führt. Weitere Informationen finden Sie im Abschnitt zum Unkenntlichmachen von Code.

Beim Erstellen der Releaseversion Ihrer App 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-Regelndateien anpassen. R8 funktioniert mit allen vorhandenen ProGuard-Regelndateien. Wenn Sie das Android Gradle-Plug-in also auf R8 umstellen, müssen Sie Ihre vorhandenen Regeln nicht ändern.

Verkürzung, Verschleierung und Optimierung aktivieren

Wenn Sie Android Studio 3.4 oder das Android Gradle-Plug-in 3.4.0 oder 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 das Schrumpfen, die Verschleierung und die Codeoptimierung standardmäßig deaktiviert. Das liegt daran, dass diese Optimierungen bei der Kompilierung die Buildzeit Ihres Projekts verlängern und zu Fehlern führen können, wenn Sie nicht ausreichend anpassen, welcher Code beibehalten werden soll.

Daher sollten Sie diese Aufgaben zur Kompilierungszeit aktivieren, wenn Sie die endgültige Version Ihrer App erstellen, die Sie vor der Veröffentlichung testen. Wenn Sie das Schrumpfen, Verschleierung und die Optimierung aktivieren möchten, fügen Sie Folgendes in das Build-Script 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"
            )
        }
    }
    ...
}

Groovy

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 den Code Ihrer App dienen. Sie können einige dieser Regeldateien zwar ändern, einige Regeln werden jedoch möglicherweise automatisch von Tools zur Kompilierungszeit wie AAPT2 generiert oder von den Bibliotheksabhängigkeiten Ihrer App übernommen. In der folgenden Tabelle werden die Quellen der ProGuard-Regelndateien beschrieben, die von R8 verwendet werden.

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 für 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, das Regeln enthält, die für die meisten Android-Projekte nützlich sind, und @Keep*-Anmerkungen ermöglicht.

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-Regelndateien. Wir empfehlen jedoch, proguard-android-optimize.txt zu verwenden.

Bibliotheksabhängigkeiten

In einer AAR-Mediathek:
proguard.txt

In einer JAR-Bibliothek:
META-INF/proguard/<ProGuard-rules-file>

Zusätzlich zu diesen Speicherorten unterstützt das Android Gradle-Plug-in 3.6 oder höher auch gezielte Minimierungsregeln.

Wenn eine AAR- oder JAR-Bibliothek mit einer eigenen Regeldatei veröffentlicht wird und Sie diese Bibliothek als Abhängigkeit zur Kompilierungszeit einschließen, werden diese Regeln von R8 automatisch beim Kompilieren Ihres Projekts angewendet.

Zusätzlich zu den herkömmlichen ProGuard-Regeln unterstützt das Android Gradle-Plug-in 3.6 oder höher auch zielgerichtete Minimierungsregeln. Dies sind Regeln, die auf bestimmte Schrumpfprogramme (R8 oder ProGuard) sowie auf bestimmte Schrumpfprogrammversionen ausgerichtet sind.

Die Verwendung von Regelndateien, die mit Bibliotheken verpackt sind, ist nützlich, wenn bestimmte Regeln für die ordnungsgemäße Funktion der Bibliothek erforderlich sind. Das bedeutet, dass der Bibliotheksentwickler die Schritte zur Fehlerbehebung für Sie ausgeführt hat.

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

Android Asset Package Tool 2 (AAPT2) Nach dem Erstellen des Projekts mit minifyEnabled true: <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 generiert Regeln zum Beibehalten basierend auf Verweis auf Klassen im Manifest, in Layouts und in anderen App-Ressourcen Ihrer App. AAPT2 enthält beispielsweise eine Beibehaltungsregel für jede Aktivität, die Sie im Manifest Ihrer App als Einstiegspunkt registrieren.
Benutzerdefinierte Konfigurationsdateien Wenn Sie in Android Studio ein neues Modul erstellen, wird standardmäßig <module-dir>/proguard-rules.pro erstellt, 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. Denken Sie daran, wenn Sie Fehler mit R8 beheben, da andere Abhängigkeiten zur Kompilierungszeit, z. B. Bibliotheksabhängigkeiten, Änderungen am R8-Verhalten verursachen können, die Ihnen nicht bekannt sind.

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

Zielgerichtete Schrumpfregeln

Das Android Gradle-Plug-in 3.6 oder höher unterstützt Bibliotheksregeln, die auf bestimmte Schrumpfer (R8 oder ProGuard) sowie auf bestimmte Schrumpferversionen ausgerichtet sind. Auf diese Weise können Bibliotheksentwickler ihre Regeln so anpassen, dass sie optimal in Projekten mit neuen Versionen zur Reduzierung der Reduzierung funktionieren. Gleichzeitig können vorhandene Regeln weiterhin in Projekten mit älteren reduzierten Versionen verwendet werden.

Um gezielte Schrumpfregeln anzugeben, müssen Bibliotheksentwickler sie an bestimmten Stellen in einer AAR- oder JAR-Bibliothek einfügen, wie unten beschrieben.

In an AAR library:
    proguard.txt (legacy location)
    classes.jar
    └── META-INF
        └── com.android.tools (targeted shrink rules location)
            ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
            └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rules-file> (legacy location)
    └── com.android.tools (targeted shrink rules location)
        ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
        └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

Das bedeutet, dass Regeln für die gezielte Verkleinerung im Verzeichnis META-INF/com.android.tools einer JAR-Datei oder im Verzeichnis META-INF/com.android.tools in classes.jar einer AAR-Datei gespeichert werden.

In diesem Verzeichnis können sich mehrere Verzeichnisse mit Namen im Format r8-from-<X>-upto-<Y> oder proguard-from-<X>-upto-<Y> befinden, die angeben, für welche Versionen der jeweiligen Schrumpfer die Regeln in den Verzeichnissen geschrieben wurden. Die Teile -from-<X> und -upto-<Y> sind optional. Die Version <Y> ist ausschließlich und die Versionsbereiche müssen fortlaufend sein.

Beispielsweise bilden r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 und r8-from-8.2.0 einen gültigen Satz von gezielten Regeln zum Verkleinern. Die Regeln im Verzeichnis r8-from-8.0.0-upto-8.2.0 werden von R8 ab Version 8.0.0 bis einschließlich Version 8.2.0 verwendet.

Anhand dieser Informationen wählt das Android Gradle-Plug-in 3.6 oder höher die Regeln aus den entsprechenden R8-Verzeichnissen aus. Wenn in einer Bibliothek keine gezielten Schrumpfregeln angegeben sind, wählt das Android-Gradle-Plug-in die Regeln aus den Legacy-Speicherorten aus (proguard.txt für AAR oder META-INF/proguard/<ProGuard-rules-file> für JAR).

Entwickler von Bibliotheken können entweder gezielte Schrumpfregeln oder alte ProGuard-Regeln in ihre Bibliotheken aufnehmen oder beide Typen, wenn sie die Kompatibilität mit dem Android Gradle-Plug-in älter als 3.6 oder anderen Tools beibehalten möchten.

Zusätzliche Konfigurationen einschließen

Wenn Sie mit Android Studio ein neues Projekt oder Modul erstellen, wird in der IDE eine <module-dir>/proguard-rules.pro-Datei erstellt, 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 können Sie 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")
        }
    }
}

Groovy

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.

Bei der Codekomprimierung (auch Tree Shaking genannt) wird Code entfernt, der laut R8 zur Laufzeit nicht erforderlich ist. So lässt sich die Größe Ihrer App erheblich reduzieren, wenn sie beispielsweise viele Bibliotheksabhängigkeiten enthält, aber nur einen kleinen Teil ihrer Funktionen nutzt.

Um den Code Ihrer App zu verkleinern, ermittelt R8 zuerst alle Einstiegspunkte in den Code Ihrer App 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 App, um einen Graphen aller Methoden, Mitgliedsvariablen und anderen Klassen zu erstellen, auf die Ihre App zur 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 App mit einer Laufzeitbibliotheksabhängigkeit. Bei der Prüfung des App-Codes stellt R8 fest, dass die Methoden foo(), faz() und bar() über den Einstiegspunkt MainActivity.class erreichbar sind. Die Klasse OkayApi.class oder ihre Methode baz() wird jedoch von Ihrer App zur Laufzeit nie verwendet. R8 entfernt diesen Code beim Schrumpfen Ihrer App.

Abbildung 1. Bei der Kompilierung erstellt R8 einen Graphen basierend auf den kombinierten Regeln zum Beibehalten Ihres Projekts, um nicht erreichbaren Code zu ermitteln.

R8 bestimmt Einstiegspunkte über -keep-Regeln in den R8-Konfigurationsdateien des Projekts. Mit diesen Regeln werden also Klassen angegeben, die R8 beim Schrumpfen Ihrer App nicht verwerfen soll. R8 betrachtet diese Klassen als mögliche Einstiegspunkte in Ihre App. Das Android Gradle-Plug-in und AAPT2 generieren automatisch Regeln zum Beibehalten, die für die meisten App-Projekte erforderlich sind, z. B. für die Aktivitäten, Ansichten und Dienste Ihrer App. Wenn Sie dieses Standardverhalten jedoch durch zusätzliche Keep-Regeln anpassen möchten, lesen Sie den Abschnitt Festlegen, welcher Code beibehalten werden soll.

Wenn Sie nur die Größe der Ressourcen Ihrer App reduzieren möchten, springen Sie zum Abschnitt Ressourcen verkleinern.

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 Bibliotheksspeicherung anpassen, wenn im Bibliothek-APK Klassen fehlen. Wenn Sie eine Bibliothek im AAR-Format erstellen und veröffentlichen, werden lokale JAR-Dateien, von denen Ihre Bibliothek abhängt, nicht in der AAR-Datei verkleinert.

Anpassen, welcher Code beibehalten werden soll

In den meisten Fällen reicht die Standard-ProGuard-Regelndatei (proguard-android-optimize.txt) aus, damit R8 nur den nicht verwendeten Code entfernt. In einigen Fällen kann R8 jedoch nicht richtig analysieren und entfernt möglicherweise Code, den Ihre App tatsächlich benötigt. In folgenden Fällen kann es vorkommen, dass Code fälschlicherweise entfernt wird:

  • Wenn Ihre App eine Methode aus der Java Native Interface (JNI) aufruft
  • Wenn Ihre App Code zur Laufzeit abruft (z. B. mit Reflection)

Beim Testen Ihrer App sollten alle Fehler aufgedeckt werden, die durch unangemessen entfernten Code verursacht wurden. Sie können auch prüfen, welcher Code entfernt wurde, indem Sie einen Bericht zum entfernten Code generieren.

Wenn Sie Fehler beheben und R8 zwingen möchten, bestimmten Code beizubehalten, fügen Sie in der ProGuard-Regelndatei eine Zeile vom Typ -keep hinzu. 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 Anmerkung ist nur verfügbar, wenn Sie die AndroidX-Anmerkungsbibliothek verwenden und die ProGuard-Regelndatei einschließen, die im Android Gradle-Plug-in enthalten ist, wie im Abschnitt zum Aktivieren des Schrumpfens beschrieben.

Bei der Verwendung der Option -keep sollten Sie viele Dinge beachten. 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 in Release-Builds Ihrer App native Codebibliotheken entfernt. Dabei werden die Symboltabelle und die Informationen zur Fehlerbehebung entfernt, die in allen von Ihrer App verwendeten nativen Bibliotheken enthalten sind. Das Entfernen von nativen Codebibliotheken führt zu erheblichen Einsparungen bei der Größe. Aufgrund der fehlenden Informationen (z. B. 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. Mit wenigen Schritten können Sie eine Datei mit nativen Debugsymbolen für Ihre App generieren und hochladen. Mit dieser Datei können Sie symbolische native Absturz-Stack-Traces (mit Klassen- und Funktionsnamen) in Android Vitals aktivieren, um Ihre App in der Produktion zu debuggen. Diese Schritte variieren je nach Version des Android Gradle-Plug-ins, das in Ihrem Projekt verwendet wird, 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 Debugsymbolebene aus:

  • Mit SYMBOL_TABLE können Sie Funktionsnamen in den symbolischen Stacktraces der Play Console abrufen. Diese Ebene unterstützt Tombstones.
  • Mit FULL können Sie Funktionsnamen, Dateien und Zeilennummern in den symbolischen Stacktraces der Play Console abrufen.

Wenn Sie Ihr Projekt als Android App Bundle erstellen, verwenden Sie die oben gezeigte Build-Einstellung build.gradle.kts, um die native Debugging-Symboldatei separat zu generieren. Laden Sie die Datei mit Symbolen 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 in der Version 4.0 oder niedriger (und andere Build-Systeme)

Im Rahmen des Build-Prozesses speichert das Android-Gradle-Plug-in eine Kopie der Bibliotheken mit Debugging-Symbolen 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. Laden Sie die symbols.zip-Datei manuell in die Google Play Console hoch.

Ressourcen verkleinern

Die Ressourcenkomprimierung funktioniert nur in Verbindung mit der Codekomprimierung. Nachdem der Code-Optimierer den gesamten nicht verwendeten Code entfernt hat, kann der Ressourcen-Optimierer ermitteln, welche Ressourcen die App noch verwendet. Dies gilt insbesondere, wenn Sie Codebibliotheken hinzufügen, die Ressourcen enthalten. Sie müssen ungenutzten Bibliothekcode entfernen, damit die Bibliotheksressourcen nicht mehr referenziert werden und somit vom Ressourcenverkleinerer 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"
            )
        }
    }
}

Groovy

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

Wenn Sie Ihre App noch nicht mit minifyEnabled zum Schrumpfen von Code erstellt haben, tun Sie dies, bevor Sie shrinkResources aktivieren. Möglicherweise müssen Sie die Datei proguard-rules.pro bearbeiten, um Klassen oder Methoden zu behalten, die dynamisch erstellt oder aufgerufen werden, bevor Sie mit dem Entfernen von Ressourcen beginnen.

Festlegen, welche Ressourcen beibehalten werden sollen

Wenn Sie bestimmte Ressourcen behalten oder verwerfen möchten, erstellen Sie in Ihrem Projekt eine XML-Datei mit dem Tag <resources> und geben Sie im Attribut tools:keep die zu behaltenden Ressourcen und im Attribut tools:discard die zu verwerfenden Ressourcen an. Für beide Attribute ist eine durch Kommas getrennte Liste von Ressourcennamen zulässig. 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/my.package.keep.xml. Diese Datei wird nicht in Ihre App eingebunden.

Hinweis:Verwenden Sie für die keep-Datei einen eindeutigen Namen. Wenn verschiedene Bibliotheken miteinander verknüpft werden, würden ihre Regeln zum Beibehalten sonst in Konflikt stehen, was zu potenziellen Problemen mit ignorierten Regeln oder nicht benötigten beibehaltenen Ressourcen führen kann.

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 in das gemeinsame Projektverzeichnis einfügen und dann für jede Buildvariante eine andere my.package.build.variant.keep.xml-Datei erstellen, wenn Sie wissen, dass eine bestimmte Ressource im Code verwendet wird (und daher nicht vom Schrumpfer entfernt wird), aber nicht für die jeweilige Buildvariante verwendet wird. Es ist auch möglich, dass die Build-Tools eine Ressource falsch 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.

Strenge 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 Ressourcenverkleinerer standardmäßig defensiv und kennzeichnet alle Ressourcen mit einem übereinstimmenden Namensformat als potenziell verwendet und nicht zum Entfernen 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 Ressourcenverkleinerer sucht auch in allen Stringkonstanten in Ihrem Code sowie in verschiedenen res/raw/-Ressourcen 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 Option jedoch deaktivieren und festlegen, dass der Ressourcenverkleinerer nur Ressourcen beibehält, die mit Sicherheit verwendet werden. Legen Sie dazu in der Datei keep.xml shrinkMode auf strict fest:

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

Wenn Sie den strengen Schrumpfmodus aktivieren und Ihr Code wie oben gezeigt auch 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-Ressourcen-Schrumpfer entfernt nur Ressourcen, auf die nicht durch Ihren App-Code verwiesen wird. Das bedeutet, dass alternative Ressourcen für verschiedene Gerätekonfigurationen nicht 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 verwenden (z. B. AppCompat oder Google Play-Dienste), enthält Ihre App alle übersetzten Sprachstrings für die Meldungen in diesen Bibliotheken, unabhängig davon, ob der Rest Ihrer App in dieselben Sprachen übersetzt wurde oder nicht. Wenn Sie nur die Sprachen beibehalten möchten, die Ihre App offiziell unterstützt, können Sie diese mithilfe der Property resConfig angeben. Alle Ressourcen für nicht angegebene Sprachen werden entfernt.

Im folgenden Snippet wird gezeigt, wie Sie Ihre Sprachressourcen auf Englisch und Französisch beschränken:

Kotlin

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

Groovy

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 eines Nutzers konfiguriert sind. Außerdem werden nur Ressourcen, die der Bildschirmdichte des Geräts entsprechen, und native Bibliotheken, die dem ABI des Geräts entsprechen, im Download berücksichtigt. Weitere Informationen finden Sie unter Konfiguration von Android-App-Bundles.

Bei älteren Apps, die mit APKs veröffentlicht werden (vor August 2021 erstellt), können Sie anpassen, welche Bildschirmdichte oder ABI-Ressourcen in Ihr APK aufgenommen werden sollen. Dazu müssen Sie mehrere APKs erstellen, die jeweils auf eine andere Gerätekonfiguration ausgerichtet sind.

Doppelte Ressourcen zusammenführen

Standardmäßig werden in Gradle auch Ressourcen mit identischem Namen zusammengeführt, 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 anhand einer unten beschriebenen Prioritätsreihenfolge die beste Datei aus den Duplikaten aus und übergibt nur diese eine Ressource an AAPT für die Verteilung im endgültigen Artefakt.

Gradle sucht an den folgenden Speicherorten nach doppelten Ressourcen:

  • Die Hauptressourcen, die mit dem Haupt-Quellsatz verknüpft sind, befinden sich in der Regel in src/main/res/.
  • Die Varianten-Overlays, die vom Build-Typ und den Build-Varianten stammen.
  • Die Abhängigkeiten des Bibliotheksprojekts.

Gradle führt doppelte Ressourcen in der folgenden absteigenden 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 in der Property sourceSet Ihrer build.gradle.kts-Datei mehrere Ressourcensätze definieren, z. B. wenn sowohl src/main/res/ als auch src/main/res2/ identische Ressourcen enthalten.

Code verschleiern

Durch die Verschleierung wird die App-Größe verringert, indem die Namen der Klassen, Methoden und Felder Ihrer App verkürzt werden. 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 App entfernt, aber bei Apps mit DEX-Dateien, die viele Klassen, Methoden und Felder indexieren, können erhebliche Einsparungen bei der Größe erzielt werden. 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. Wenn Sie den Stacktrace nach der Verschleierung verstehen möchten, lesen Sie den Abschnitt zum Decodieren eines verschleierten Stacktraces.

Wenn Ihr Code außerdem auf einer vorhersehbaren Benennung der Methoden und Klassen Ihrer App basiert, sollten Sie diese Signaturen beispielsweise bei der Verwendung von Reflection als Einstiegspunkte behandeln und für sie Beibehaltungsregeln angeben, wie im Abschnitt Anpassen, welcher Code beibehalten werden soll 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 bis unmöglich, einen Stack-Trace zu verstehen, da die Namen von Klassen und Methoden möglicherweise geändert wurden. Wenn Sie den ursprünglichen Stack-Trace abrufen möchten, sollten Sie den Stack-Trace noch einmal aufrufen.

Codeoptimierung

Um Ihre App noch weiter zu optimieren, prüft R8 Ihren Code auf einer tieferen Ebene, um nicht verwendeten Code zu entfernen oder den Code 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 eine Methode in Ihrem Code nur an wenigen Stellen aufgerufen wird, entfernt R8 sie möglicherweise und fügt sie an den wenigen Aufrufstellen inline ein.
  • 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 App 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 werden von R8 alle ProGuard-Regeln ignoriert, die versuchen, Standardoptimierungen zu ändern, z. B. -optimizations und -optimizationpasses. Diese Einschränkung ist wichtig, da R8 kontinuierlich verbessert wird. Wenn Sie ein Standardverhalten für Optimierungen beibehalten, kann das Android Studio-Team Probleme leichter beheben.

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 Schrumpfen, Verschleierung und Optimierung aktiviert sind, verbessert R8 die Laufzeitleistung von Code (einschließlich Start- und Framezeit im UI-Thread) um bis zu 30%. Wenn Sie eine dieser Optionen deaktivieren, werden die Optimierungen, die in R8 verwendet werden, stark eingeschränkt.

Wenn R8 aktiviert ist, sollten Sie auch Startprofile erstellen, um die Startleistung weiter zu verbessern.

Verbesserte Optimierungen aktivieren

R8 enthält eine Reihe zusätzlicher Optimierungen (als „Vollmodus“ bezeichnet), die sich von ProGuard unterscheiden. 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 Datei gradle.properties Ihres Projekts einfügen:

android.enableR8.fullMode=false

Da sich R8 aufgrund der zusätzlichen Optimierungen anders als ProGuard verhält, müssen Sie möglicherweise zusätzliche ProGuard-Regeln einschließen, um Laufzeitprobleme zu vermeiden, wenn Sie Regeln verwenden, die für ProGuard entwickelt wurden. Angenommen, Ihr Code verweist über die Java Reflection API auf eine Klasse. Wenn Sie keinen Vollmodus verwenden, geht R8 davon aus, dass Sie Objekte dieser Klasse zur Laufzeit prüfen und manipulieren möchten – auch wenn Ihr Code dies nicht tut – und behält die Klasse und ihre statische Initialisierung automatisch bei.

Im „Vollmodus“ geht R8 jedoch nicht von dieser Annahme aus. Wenn R8 feststellt, dass die Klasse in Ihrem Code sonst nie zur Laufzeit verwendet wird, wird sie aus der endgültigen DEX-Datei Ihrer App 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 Vollmodus von R8 Probleme auftreten, findest du auf der FAQ-Seite zu R8 eine mögliche Lösung. Wenn Sie das Problem nicht beheben können, melden Sie bitte einen Fehler.

Stacktraces zurückverfolgen

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. Der größte Faktor ist die Verschleierung, bei der sogar die Namen der Klassen und Methoden geändert werden.

Zum Wiederherstellen des ursprünglichen Stack-Traces bietet R8 das Befehlszeilentool retrace, das im Paket mit Befehlszeilentools enthalten ist.

Damit die Stack-Traces Ihrer Anwendung nachverfolgt werden können, sollten Sie dafür sorgen, dass im Build genügend Informationen für die Nachverfolgung gespeichert werden. Fügen Sie dazu der proguard-rules.pro-Datei 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 ausdrucken. Mit der Direktive -renamesourcefileattribute wird der Name der Quelldatei in Stack-Traces auf SourceFile festgelegt. Der tatsächliche Name der ursprünglichen Quelldatei ist beim Zurückverfolgen 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 Sie Ihre App bei Google Play veröffentlichen, können Sie die Datei mapping.txt für jede Version Ihrer App hochladen. Bei der Veröffentlichung mit Android App Bundles wird diese Datei automatisch als Teil des App-Bundle-Inhalts eingeschlossen. Anschließend werden von Google Play eingehende Stack-Traces von von Nutzern gemeldeten Problemen zurückverfolgt, damit Sie sie in der Play Console prüfen können. Weitere Informationen finden Sie im Hilfeartikel Deobfuscierung von Crash-Stack-Traces.

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 FAQs zu R8 und die Anleitung zur Fehlerbehebung für 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 App erstellen, gibt R8 einen Bericht mit dem von Ihnen 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 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
...

Fehlerbehebung bei Entfernung von Ressourcen

Wenn Sie Ressourcen verkleinern, wird im Fenster Build eine Zusammenfassung der Ressourcen angezeigt, die aus der App entfernt werden. Sie müssen zuerst auf der linken Seite des Fensters auf Ansicht wechseln klicken, um eine detaillierte Textausgabe von Gradle zu sehen. Beispiel:

:android:shrinkDebugResources
Removed unused resources: 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 dazu, welche Ressourcen auf andere Ressourcen verweisen und welche Ressourcen verwendet oder entfernt werden.

Wenn Sie beispielsweise wissen möchten, warum @drawable/ic_plus_anim_016 noch in Ihrer App vorhanden ist, öffnen Sie die Datei resources.txt und suchen Sie nach diesem Dateinamen. Möglicherweise wird von einer anderen Ressource auf sie verwiesen:

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öchten jetzt wissen, warum @drawable/add_schedule_fab_icon_anim erreichbar ist. Wenn Sie nach oben suchen, sehen Sie, dass die Ressource unter „Die über die Wurzel erreichbaren Ressourcen sind:“ aufgeführt ist. Das bedeutet, dass es eine Codereferenz auf add_schedule_fab_icon_anim gibt, d. h., die R.drawable-ID wurde im erreichbaren Code gefunden.

Wenn Sie keine strenge Prüfung verwenden, können Ressourcen-IDs als erreichbar gekennzeichnet werden, wenn es Stringkonstanten gibt, die so aussehen, als könnten 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 er nicht zum dynamischen Laden der entsprechenden Ressource verwendet wird, können Sie das Build-System mit dem Attribut tools:discard darüber informieren, dass er entfernt werden soll. Wie das geht, wird im Abschnitt Anpassen, welche Ressourcen beibehalten werden sollen beschrieben.