App verkleinern, verschleiern und optimieren

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

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. Das 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:Hiermit werden ungenutzte Ressourcen aus Ihrer verpackten App entfernt, einschließlich ungenutzter Ressourcen in den Bibliotheksabhängigkeiten Ihrer App. Sie funktioniert in Kombination mit der Codekomprimierung, sodass nach dem Entfernen nicht verwendeter Codeelemente auch alle Ressourcen, auf die nicht mehr verwiesen wird, sicher entfernt werden können. 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 Ihr 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-Regelndateien, 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 mit Android Studio ein neues Modul erstellen, wird in der IDE im Stammverzeichnis dieses Moduls eine Datei proguard-rules.pro erstellt.

Standardmäßig werden in dieser Datei keine Regeln angewendet. Fügen Sie hier also Ihre eigenen ProGuard-Regeln hinzu, z. B. Ihre benutzerdefinierten Regeln für das Behalten von Code.

Android-Gradle-Plug-in Wird vom Android-Gradle-Plug-in zur Kompilierungszeit 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, wird diese Regelndatei standardmäßig vom Build-Script auf Modulebene in Ihren Release-Build eingefügt.

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 Entwickler der Bibliothek 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 einschließen, die dann von R8 zur Kompilierungszeit angewendet werden.

Wenn Sie die Property minifyEnabled auf true festlegen, werden in R8 Regeln aus allen oben aufgeführten verfügbaren Quellen kombiniert. 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.

Wenn Sie einen vollständigen Bericht zu 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

Regeln für gezieltes Schrumpfen

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. So können Bibliotheksentwickler ihre Regeln so anpassen, dass sie optimal in Projekten mit neuen Shrinker-Versionen funktionieren, während vorhandene Regeln weiterhin in Projekten mit älteren Shrinker-Versionen verwendet werden können.

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.

Unter 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 eine gültige Gruppe von Regeln für gezielte Verkleinerungen. 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 für eine Bibliothek keine gezielten Minimierungsregeln angegeben sind, wählt das Android Gradle-Plug-in die Regeln aus den bisherigen Speicherorten aus (proguard.txt für eine AAR oder META-INF/proguard/<ProGuard-rules-file> für eine 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 einschließen, indem Sie sie der Eigenschaft proguardFiles im Build-Script Ihres Moduls hinzufügen.

Sie können beispielsweise Regeln hinzufügen, die für jede Buildvariante spezifisch sind, indem Sie im entsprechenden productFlavor-Block eine weitere proguardFiles-Property hinzufügen. In der folgenden Gradle-Datei wird der Produktvariante flavor2 flavor2-rules.pro hinzugefügt. Jetzt werden für flavor2 alle drei ProGuard-Regeln verwendet, da auch die 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 Schrumpfen von Code mit R8 ist standardmäßig aktiviert, wenn Sie die Eigenschaft 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 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 diesem Graphen verbunden ist, gilt als nicht erreichbar 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 mit zusätzlichen Aufbewahrungsregeln anpassen möchten, lesen Sie den Abschnitt Anpassen, 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 wird, enthält eine App, die von dieser Bibliothek abhängt, verkleinerte Bibliotheksklassen. Möglicherweise müssen Sie die Regeln für die Beibehaltung von Bibliotheken 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.

Festlegen, 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, was entfernt werden soll, und es wird möglicherweise Code entfernt, der für Ihre App tatsächlich erforderlich ist. 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 sucht (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 dem Code, den Sie behalten möchten, die Anmerkung @Keep hinzufügen. Wenn Sie @Keep einer Klasse hinzufügen, bleibt die gesamte Klasse unverändert. Wenn Sie es einer Methode oder einem Feld hinzufügen, bleiben die Methode/das Feld (und ihr Name) sowie der Klassenname unverändert. 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.

Unterstützung für native 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 Ihr Projekt als Android App Bundle erstellen, können Sie die native Debugging-Symboldatei automatisch darin aufnehmen lassen. Wenn Sie diese Datei in Release-Builds einbeziehen 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 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 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.

Wenn Sie die Ressourcenkomprimierung aktivieren möchten, legen Sie in Ihrem Build-Script die Eigenschaft shrinkResources auf true fest (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"
            )
        }
    }
}

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 Ressourcen an, die Sie behalten möchten, und im Attribut tools:discard die Ressourcen, die Sie verwerfen möchten. 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 verpackt.

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.

Es mag unsinnig erscheinen, anzugeben, welche Ressourcen verworfen werden sollen, wenn Sie sie stattdessen löschen könnten. Dies kann jedoch bei der Verwendung von Buildvarianten 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 fälschlicherweise als erforderlich identifiziert haben. Das ist möglich, weil der Compiler die Ressourcen-IDs inline einfügt und der Ressourcenanalysator dann möglicherweise nicht zwischen einer tatsächlich referenzierten Ressource und einem Ganzzahlwert im Code unterscheiden kann, der zufällig denselben Wert hat.

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 – die AppCompat-Bibliothek tut dies) bedeutet das, dass Ihr Code Ressourcennamen basierend auf dynamisch generierten Strings sucht. 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.

Mit dem folgenden Code werden beispielsweise alle Ressourcen mit dem Präfix img_ als verwendet markiert.

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 Strings wie dieser oder andere gefunden werden, die dazu verwendet werden könnten, URLs wie diese zu erstellen, werden sie nicht entfernt.

Dies sind Beispiele für den sicheren Verkleinerungsmodus, der standardmäßig aktiviert ist. Sie können diese Sicherheitsmaßnahme jedoch deaktivieren und angeben, dass der Ressourcenverkleinerer nur Ressourcen beibehalten soll, 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. Falls erforderlich, können Sie mit der resConfigs-Eigenschaft des Android Gradle-Plug-ins alternative Ressourcendateien 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 Sie eine App im Android App Bundle-Format veröffentlichen, werden beim Installieren der App standardmäßig nur die auf dem Gerät eines Nutzers konfigurierten Sprachen heruntergeladen. Ebenso sind nur Ressourcen enthalten, die der Bildschirmdichte des Geräts entsprechen, und native Bibliotheken, die dem ABI des Geräts entsprechen. 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 durch die shrinkResources-Eigenschaft gesteuert und kann nicht deaktiviert werden, da Fehler vermieden werden müssen, wenn mehrere Ressourcen mit dem Namen übereinstimmen, der in Ihrem Code gesucht wird.

Die Ressourcenzusammenführung erfolgt nur, wenn zwei oder mehr Dateien denselben Ressourcennamen, Typ und Qualifier haben. Gradle wählt anhand der 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:

„Dependencies“ (Abhängigkeiten) → „Main“ (Haupt) → „Build flavor“ (Build-Variante) → „Build type“ (Build-Typ)

Wenn sich beispielsweise eine doppelte Ressource sowohl in Ihren Hauptressourcen als auch in einer Build-Variante befindet, wird von Gradle diejenige in der Build-Variante ausgewählt.

Wenn identische Ressourcen im selben Quellsatz vorhanden sind, kann Gradle sie nicht zusammenführen und gibt einen Fehler beim Zusammenführen von Ressourcen aus. 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 unkenntlich machen

Durch die Verschleierung wird die App-Größe verringert, indem die Namen der Klassen, Methoden und Felder Ihrer App gekürzt werden. Im folgenden Beispiel wird eine Verschleierung mit R8 verwendet:

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 Größeneinsparungen erzielt werden. Da durch die Verschleierung verschiedene Teile Ihres Codes umbenannt werden, erfordern bestimmte Aufgaben wie die Prüfung von Stack-Traces zusätzliche Tools. 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. Mit diesen Regeln wird R8 angewiesen, diesen Code nicht nur im endgültigen DEX Ihrer App beizubehalten, sondern auch die ursprüngliche Benennung 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 weiteren nicht verwendeten Code zu entfernen oder, sofern möglich, Ihren Code umzuschreiben, um ihn prägnanter zu gestalten. Hier einige Beispiele für solche Optimierungen:

  • Wenn Ihr Code für eine bestimmte if/else-Anweisung nie den else {}-Zweig ausführt, 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.

In R8 können Sie keine einzelnen Optimierungen deaktivieren oder aktivieren und auch das Verhalten einer Optimierung nicht ä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, ändern sich die Stack-Traces für Ihre Anwendung. Durch Inline-Einfügen werden beispielsweise Stackframes entfernt. Im Abschnitt Retrace erfahren Sie, wie Sie die ursprünglichen Stack-Traces abrufen.

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. Wenn Sie also die Klasse und ihren statischen Initializer beibehalten möchten, müssen Sie in Ihrer Regelndatei eine entsprechende Regel angeben.

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 Arten geändert, was die Lesbarkeit von Stack-Traces erschweren kann, da sie nicht genau dem Quellcode entsprechen. Das kann bei Änderungen an den Zeilennummern der Fall sein, wenn Informationen zur Fehlerbehebung nicht beibehalten werden. Das kann an Optimierungen wie Einfügen und Umranden liegen. 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, sodass diese Positionen in Stacktraces ausgegeben werden. Das Attribut SourceFile sorgt dafür, dass alle potenziellen Laufzeiten die Positionierungsinformationen 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 jedem Lauf eine mapping.txt-Datei, die die Informationen enthält, die zum Zuordnen von Stack-Traces zu den ursprünglichen Stack-Traces 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 Fehlerbehebung bei Problemen beschrieben, die beim Aktivieren von Schrumpfen, Verschleierung und Optimierung mit R8 auftreten. 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 zum entfernten (oder beibehaltenen) Code generieren

Zur Behebung bestimmter R8-Probleme kann es hilfreich sein, einen Bericht mit dem gesamten Code zu sehen, der von R8 aus Ihrer App entfernt wurde. Fügen Sie Ihrer Datei mit benutzerdefinierten Regeln für jedes Modul, für das Sie diesen Bericht generieren möchten, -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 zu den Einstiegspunkten sehen möchten, die R8 anhand der Beibehaltungsregeln Ihres Projekts ermittelt, fügen Sie -printseeds <output-dir>/seeds.txt in die Datei mit benutzerdefinierten Regeln ein. Wenn Sie R8 aktivieren und Ihre App erstellen, gibt R8 einen Bericht mit dem von Ihnen angegebenen Pfad und Dateinamen aus. Der Bericht zu behaltenen Eintragspunkten 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 umschalten 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 namens resources.txt in <module-name>/build/outputs/mapping/release/ (im selben 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 darauf von einer anderen Ressource verwiesen, z. B. so:

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 in der Build-Ausgabe nach dem Ressourcennamen suchen, sehen 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 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.