Build-Geschwindigkeit optimieren

Lange Build-Zeiten verlangsamen Ihren Entwicklungsprozess. Auf dieser Seite werden einige Techniken zur Behebung von Build-Geschwindigkeitsengpässen erläutert.

Im Allgemeinen können Sie die Build-Geschwindigkeit Ihrer App wie folgt verbessern:

  1. Optimieren Sie Ihre Build-Konfiguration, indem Sie einige Schritte ausführen, von denen die meisten Android Studio-Projekte sofort profitieren.
  2. Erstellen Sie ein Profil für den Build, um einige der kniffligeren Engpässe zu identifizieren und zu diagnostizieren, die möglicherweise bei Ihrem Projekt oder Ihrer Workstation auftreten.

Stellen Sie Ihre App bei der Entwicklung nach Möglichkeit auf einem Gerät mit Android 7.0 (API-Level 24) oder höher bereit. Neuere Versionen der Android-Plattform implementieren bessere Mechanismen zum Übertragen von Updates in Ihre App, z. B. Android Runtime (ART) und native Unterstützung für mehrere DEX-Dateien.

Hinweis: Nach dem ersten sauberen Build stellen Sie möglicherweise fest, dass nachfolgende Builds, sowohl saubere als auch inkrementelle Builds, viel schneller ausgeführt werden, auch wenn keine der auf dieser Seite beschriebenen Optimierungen angewendet werden. Das liegt daran, dass der Gradle-Daemon eine Aufwärmphase mit zunehmender Leistung hat – ähnlich wie bei anderen JVM-Prozessen.

Build-Konfiguration optimieren

Mit den folgenden Tipps können Sie die Build-Geschwindigkeit für Ihr Android Studio-Projekt verbessern.

Tools auf dem neuesten Stand halten

Die Android-Tools erhalten bei fast jedem Update Build-Optimierungen und neue Funktionen. Bei einigen Tipps auf dieser Seite wird davon ausgegangen, dass Sie die neueste Version verwenden. Damit du von den neuesten Optimierungen profitieren kannst, solltest du Folgendes immer auf dem neuesten Stand halten:

KSP anstelle von kapt verwenden

Das Kotlin-Anmerkungsverarbeitungstool (kapt) ist deutlich langsamer als der Kotlin-Symbolprozessor (KSP). Wenn Sie eine Kotlin-Quelle mit Anmerkungen schreiben und Tools verwenden, die Anmerkungen verarbeiten (z. B. Room), die KSP unterstützen, sollten Sie zu KSP migrieren.

Kompilieren unnötiger Ressourcen vermeiden

Kompilieren und verpacken Sie keine Ressourcen, die Sie nicht testen, z. B. zusätzliche Sprachlokalisierungen und Ressourcen zur Bildschirmdichte. Gib stattdessen nur eine Sprachressource und eine Bildschirmdichte für deine „dev“-Variante an, wie im folgenden Beispiel gezeigt:

Groovig

android {
    ...
    productFlavors {
        dev {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Gradle-Plug-in-Portal als Letztes positionieren

Bei Android befinden sich alle Plug-ins in den Repositories google() und mavenCentral(). Ihr Build benötigt jedoch möglicherweise Drittanbieter-Plug-ins, die mit dem Dienst gradlePluginPortal() aufgelöst werden.

Gradle durchsucht Repositories in der Reihenfolge, in der sie deklariert sind. Die Build-Leistung wird also verbessert, wenn die aufgeführten Repositories die meisten Plug-ins enthalten. Experimentieren Sie daher mit dem Eintrag gradlePluginPortal(). Fügen Sie ihn dazu an letzter Stelle in den Repository-Block Ihrer settings.gradle-Datei ein. In den meisten Fällen minimiert dies die Anzahl der Suchen nach redundanten Plug-ins und verbessert die Build-Geschwindigkeit.

Weitere Informationen dazu, wie Gradle mehrere Repositories verwendet, findest du in der Gradle-Dokumentation unter Mehrere Repositories deklarieren.

Statische Build-Konfigurationswerte mit dem Debug-Build verwenden

Verwenden Sie immer statische Werte für Attribute, die in der Manifestdatei oder den Ressourcendateien für den Build-Typ zur Fehlerbehebung enthalten sind.

Wenn Sie dynamische Versionscodes, Versionsnamen, Ressourcen oder eine andere Build-Logik verwenden, die die Manifestdatei ändert, ist jedes Mal ein vollständiger Anwendungs-Build erforderlich, wenn Sie eine Änderung ausführen möchten, auch wenn für die eigentliche Änderung sonst möglicherweise nur ein Hot-Swap erforderlich ist. Wenn Ihre Build-Konfiguration solche dynamischen Attribute erfordert, isolieren Sie diese Attribute für Ihre Release-Build-Varianten und halten Sie die Werte für Ihre Debug-Builds statisch, wie im folgenden Beispiel gezeigt:

  ...
  // Use a filter to apply onVariants() to a subset of the variants.
  onVariants(selector().withBuildType("release")) { variant ->
      // Because an app module can have multiple outputs when using multi-APK, versionCode
      // is only available on the variant output.
      // Gather the output when we are in single mode and there is no multi-APK.
      val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

      // Create the version code generating task.
      val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
          it.outputFile.set(project.layout.buildDirectory.file("versionCode${variant.name}.txt"))
      }

      // Wire the version code from the task output.
      // map will create a lazy Provider that:
      // 1. Runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
      //    and therefore the file is created.
      // 2. Contains task dependency information so that the consumer(s) run after the producer.
      mainOutput.versionCode.set(versionCodeTask.flatMap { it.outputFile.map { it.asFile.readText().toInt() } })
  }
  ...

  abstract class VersionCodeTask : DefaultTask() {

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun action() {
        outputFile.get().asFile.writeText("1.1.1")
    }
  }

Informationen zum Festlegen eines dynamischen Versionscodes in Ihrem Projekt finden Sie im setVersionsFromTask-Rezept auf GitHub.

Statische Abhängigkeitsversionen verwenden

Wenn Sie Abhängigkeiten in Ihren build.gradle-Dateien deklarieren, sollten Sie keine dynamischen Versionsnummern verwenden, also Nummern mit einem Pluszeichen am Ende, z. B. 'com.android.tools.build:gradle:2.+'. Die Verwendung dynamischer Versionsnummern kann zu unerwarteten Versionsupdates, Problemen beim Auflösen von Versionsunterschieden und verlangsamten Builds führen, wenn Gradle nach Updates sucht. Verwenden Sie stattdessen statische Versionsnummern.

Bibliotheksmodule erstellen

Suchen Sie in Ihrer App nach Code, den Sie in ein Android-Bibliotheksmodul umwandeln können. Wenn Sie Ihren Code auf diese Weise modularisieren, kann das Build-System nur die von Ihnen geänderten Module kompilieren und diese Ausgaben für zukünftige Builds im Cache speichern. Modularisierung macht auch die parallele Projektausführung effektiver, wenn Sie diese Optimierung aktivieren.

Aufgaben für benutzerdefinierte Build-Logik erstellen

Wenn du nach dem Erstellen eines Build-Profils aus dem Build-Profil hervorgeht, dass ein relativ langer Teil der Build-Zeit in der Phase **Projekte konfigurieren** ist, prüfe deine build.gradle-Skripts und suche nach Code für eine benutzerdefinierte Gradle-Aufgabe. Durch das Verschieben von Build-Logik in eine Aufgabe sorgen Sie dafür, dass die Aufgabe nur bei Bedarf ausgeführt wird, Ergebnisse für nachfolgende Builds im Cache gespeichert werden können und diese Build-Logik parallel ausgeführt werden kann, wenn Sie die parallele Projektausführung aktivieren. Weitere Informationen zu Takts für benutzerdefinierte Build-Logik finden Sie in der offiziellen Gradle-Dokumentation.

Tipp: Wenn Ihr Build eine große Anzahl von benutzerdefinierten Aufgaben enthält, können Sie die build.gradle-Dateien durch Erstellen benutzerdefinierter Aufgabenklassen übersichtlicher gestalten. Füge deine Klassen dem Verzeichnis project-root/buildSrc/src/main/groovy/ hinzu. Gradle fügt diese Klassen automatisch in den Klassenpfad für alle build.gradle-Dateien in deinem Projekt ein.

Bilder in WebP konvertieren

WebP ist ein Bilddateiformat, das eine verlustbehaftete Komprimierung (wie JPEG) und Transparenz (wie PNG) bietet. WebP bietet eine bessere Komprimierung als JPEG oder PNG.

Wenn Sie die Größe von Bilddateien verringern, ohne die Build-Komprimierung vorzunehmen, kann dies Ihre Builds beschleunigen, insbesondere wenn Ihre Anwendung viele Image-Ressourcen verwendet. Unter Umständen stellen Sie beim Dekomprimieren von WebP-Bildern jedoch einen leichten Anstieg der CPU-Auslastung des Geräts fest. Mit Android Studio kannst du deine Bilder ganz einfach in WebP konvertieren.

PNG-Bearbeitung deaktivieren

Wenn du deine PNG-Bilder nicht in WebP konvertieren möchtest, kannst du deinen Build trotzdem beschleunigen, indem du die automatische Bildkomprimierung bei jedem Build der Anwendung deaktivierst.

Wenn Sie das Android Gradle-Plug-in 3.0.0 oder höher verwenden, ist die PNG-Funktion für den Build-Typ „Debug“ standardmäßig deaktiviert. Um diese Optimierung für andere Build-Typen zu deaktivieren, fügen Sie der Datei build.gradle Folgendes hinzu:

Groovig

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

Da diese Eigenschaft nicht von Build-Typen oder Produktvarianten definiert wird, müssen Sie sie beim Erstellen der Release-Version Ihrer App manuell auf true setzen.

Mit der parallelen JVM-automatischen Speicherbereinigung experimentieren

Die Build-Leistung kann verbessert werden, indem die optimale von Gradle verwendete JVM-Speicherbereinigung konfiguriert wird. Während JDK 8 standardmäßig für die parallele automatische Speicherbereinigung konfiguriert ist, sind JDK 9 und höher für die Verwendung der G1-automatischen Speicherbereinigung konfiguriert.

Wenn Sie die Build-Leistung verbessern möchten, empfehlen wir, Ihre Gradle-Builds mit der parallelen Speicherbereinigung zu testen. Legen Sie in gradle.properties Folgendes fest:

org.gradle.jvmargs=-XX:+UseParallelGC

Wenn in diesem Feld bereits andere Optionen festgelegt sind, fügen Sie eine neue hinzu:

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

Informationen zum Messen der Build-Geschwindigkeit mit verschiedenen Konfigurationen finden Sie unter Build-Profil erstellen.

JVM-Heap-Größe erhöhen

Wenn Sie langsame Builds beobachten und insbesondere die automatische Speicherbereinigung in den Build Analyzer-Ergebnissen mehr als 15% der Build-Zeit in Anspruch nimmt, sollten Sie die Größe des Java Virtual Machine (JVM)-Heaps erhöhen. Legen Sie in der Datei gradle.properties das Limit auf 4, 6 oder 8 Gigabyte fest, wie im folgenden Beispiel gezeigt:

org.gradle.jvmargs=-Xmx6g

Testen Sie dann, ob sich die Build-Geschwindigkeit verbessert hat. Die optimale Heap-Größe lässt sich am einfachsten ermitteln, indem Sie das Limit um einen kleinen Wert erhöhen und dann testen, ob eine ausreichende Verbesserung der Build-Geschwindigkeit möglich ist.

Wenn Sie auch den parallelen JVM-Garbage Collector verwenden, sollte die gesamte Zeile so aussehen:

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

Sie können die JVM-Arbeitsspeicherfehler analysieren, indem Sie das Flag HeapDumpOnOutOfMemoryError aktivieren. Dadurch generiert die JVM einen Heap-Dump, wenn nicht mehr genügend Arbeitsspeicher verfügbar ist.

Nicht-transitive R-Klassen verwenden

Verwenden Sie nicht Transitive R-Klassen, um schnellere Builds für Anwendungen mit mehreren Modulen zu erzielen. Auf diese Weise wird eine Duplizierung von Ressourcen verhindert, da die R-Klasse jedes Moduls nur Verweise auf die eigenen Ressourcen enthält, ohne Verweise aus den Abhängigkeiten abzurufen. Dies führt zu schnelleren Builds und bietet die entsprechenden Vorteile der Kompilierungsvermeidung. Dies ist das Standardverhalten im Android-Gradle-Plug-in 8.0.0 und höher.

Ab Android Studio Bumblebee sind nicht-transitive R-Klassen für neue Projekte standardmäßig aktiviert. Aktualisieren Sie Projekte, die mit früheren Versionen von Android Studio erstellt wurden, so, dass sie nicht-transitive R-Klassen verwenden. Rufen Sie dazu Refaktorieren > Zu nicht-transitiven R-Klassen migrieren auf.

Weitere Informationen zu Anwendungsressourcen und zur Klasse R finden Sie unter Übersicht über Anwendungsressourcen.

Nicht konstante R-Klassen verwenden

Verwenden Sie nicht konstante Felder der R-Klasse in Anwendungen und Tests, um die Inkrementalität der Java-Kompilierung zu verbessern und eine präzisere Ressourcenverkürzung zu ermöglichen. R-Klassenfelder sind für Bibliotheken immer nicht konstant, da die Ressourcen beim Packen des APKs für die App oder den Test, der von dieser Bibliothek abhängt, nummeriert werden. Dies ist das Standardverhalten im Android-Gradle-Plug-in 8.0.0 und höher.

Jetifier-Flag deaktivieren

Da die meisten Projekte AndroidX-Bibliotheken direkt verwenden, können Sie das Jetifier-Flag entfernen, um die Build-Leistung zu verbessern. Zum Entfernen des Jetifier-Flags legen Sie android.enableJetifier=false in der Datei gradle.properties fest.

Build Analyzer kann prüfen, ob das Flag sicher entfernt werden kann, damit Ihr Projekt eine bessere Build-Leistung erzielt und es von den nicht verwalteten Android-Supportbibliotheken weggeleitet wird. Weitere Informationen zum Build Analyzer finden Sie unter Probleme mit der Build-Leistung beheben.

Konfigurationscache verwenden

Im Konfigurations-Cache kann Gradle Informationen über die Build-Aufgabengrafik aufzeichnen und in nachfolgenden Builds wiederverwenden. So muss nicht der gesamte Build noch einmal neu konfiguriert werden.

Gehen Sie folgendermaßen vor, um den Konfigurationscache zu aktivieren:

  1. Prüfen Sie, ob alle Projekt-Plug-ins kompatibel sind.

    Prüfen Sie mit dem Build Analyzer, ob Ihr Projekt mit dem Konfigurationscache kompatibel ist. Build Analyzer führt eine Reihe von Test-Builds aus, um festzustellen, ob das Feature für das Projekt aktiviert werden kann. Unter Problem Nr. 13490 finden Sie eine Liste der unterstützten Plug-ins.

  2. Fügen Sie der Datei gradle.properties den folgenden Code hinzu:

      org.gradle.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.configuration-cache.problems=warn

Wenn der Konfigurationscache aktiviert ist, wird bei der ersten Ausführung Ihres Projekts in der Build-Ausgabe Calculating task graph as no configuration cache is available for tasks angezeigt. Bei nachfolgenden Ausführungen wird in der Build-Ausgabe Reusing configuration cache angezeigt.

Weitere Informationen zum Konfigurationscache finden Sie im Blogpost Detaillierte Informationen zum Konfigurations-Caching und in der Gradle-Dokumentation zum Konfigurationscache.

Probleme mit dem Konfigurationscache in Gradle 8.1 und Android Gradle-Plug-in 8.1 eingeführt

Der Konfigurationscache wurde in Gradle 8.1 stabil und ermöglichte das File API-Tracking. Aufrufe wie File.exists(), File.isDirectory() und File.list() werden von Gradle aufgezeichnet, um Konfigurationseingabedateien zu verfolgen.

Das Android Gradle-Plug-in (AGP) 8.1 verwendet diese File-APIs für einige Dateien, die Gradle nicht als Cache-Eingaben betrachten soll. Dies löst bei der Verwendung mit Gradle 8.1 und höher eine zusätzliche Cache-Entwertung aus, was die Build-Leistung verlangsamt. Die folgenden Eingaben werden in AGP 8.1 als Cache-Eingaben behandelt:

Eingang Issue Tracker Behoben in
$GRADLE_USER_HOME/android/FakeDependency.jar Problem #289232054 AGP 8.2
cmake-Ausgabe Problem Nr. 287676077 AGP 8.2
$GRADLE_USER_HOME/.android/analytics.settings Problem Nr. 278767328 AGP 8.3

Wenn Sie diese APIs oder ein Plug-in verwenden, das diese APIs verwendet, kommt es möglicherweise zu einer Regression der Build-Zeit, da einige Build-Logik, die diese APIs verwendet, eine zusätzliche Cache-Entwertung auslösen kann. Informationen zu diesen Mustern und zur Korrektur der Build-Logik oder zur vorübergehenden Deaktivierung des Datei-API-Trackings finden Sie unter Verbesserungen beim Eingabe-Tracking der Build-Konfiguration.