Profil für Build erstellen

Bei größeren Projekten oder solchen mit einer großen Anzahl von benutzerdefinierten Build-Logiken müssen Sie sich eventuell genauer mit dem Build-Prozess befassen, um Engpässe zu erkennen. Erstellen Sie dazu ein Profil dafür, wie lange Gradle zum Ausführen der einzelnen Phasen des Build-Lebenszyklus und jeder Build-Aufgabe benötigt. Wenn Ihr Build-Profil beispielsweise anzeigt, dass Gradle zu viel Zeit mit der Konfiguration Ihres Projekts verbringt, wird Ihnen möglicherweise vorgeschlagen, benutzerdefinierte Build-Logik aus der Konfigurationsphase zu entfernen. Wenn die Aufgabe mergeDevDebugResources einen großen Teil der Build-Zeit in Anspruch nimmt, kann dies außerdem darauf hindeuten, dass Sie entweder Ihre Images in WebP konvertieren oder die Datenverarbeitung durch PNG deaktivieren müssen.

Wenn Sie Android Studio 4.0 oder höher verwenden, können Sie Probleme mit der Build-Leistung am besten mithilfe des Build Analyzers untersuchen.

Darüber hinaus gibt es zwei Optionen für die Profilerstellung Ihres Builds außerhalb von Android Studio:

  1. Das eigenständige gradle-profiler-Tool ist ein robustes Tool für die detaillierte Analyse Ihres Builds.

  2. Die Gradle-Option --profile, ein praktisches Tool, das über die Gradle-Befehlszeile verfügbar ist.

Das eigenständige gradle-profiler-Tool verwenden

Mit dem Gradle Profiler, einem Tool zum Erfassen von Profiling- und Benchmarking-Informationen für Gradle-Builds, können Sie die Projekteinrichtung mit der besten Build-Geschwindigkeit finden. Mit dem Gradle-Profiler können Sie Build-Szenarien erstellen und mehrmals ausführen. Dadurch werden starke Abweichungen zwischen den Ergebnissen vermieden und die Ergebnisse reproduzierbar.

Der Benchmarking-Modus sollte verwendet werden, um Informationen zu sauberen und inkrementellen Builds zu erfassen. Mit dem Profilerstellungsmodus können Sie detailliertere Informationen zu den Ausführungen erfassen, einschließlich CPU-Snapshots.

Einige der Projekteinrichtungskonfigurationen für das Benchmarking:

  • Plug-in-Versionen
  • Gradle-Versionen
  • JVM-Einstellungen (Heap-Größe, Permgen-Größe, automatische Speicherbereinigung usw.)
  • Anzahl der Gradle-Worker (org.gradle.workers.max)
  • Optionen pro Plug-in zur weiteren Leistungsoptimierung

Erste Schritte

  • Installieren Sie gradle-profiler mithilfe dieser Anleitung.
  • Ausführen: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Dies gilt als Benchmark für einen vollständig aktuellen Build, da --benchmark die Aufgabe mehrmals ausführt, ohne das Projekt dazwischen zu ändern. Anschließend wird im Verzeichnis profile-out/ ein HTML-Bericht mit den Build-Zeiten generiert.

Es gibt andere Szenarien, die für das Benchmarking möglicherweise hilfreicher sind:

  • Codeänderungen im Methodentext einer Klasse, in der Sie die meiste Arbeit erledigen.
  • API-Änderungen in einem Modul, das während Ihres gesamten Projekts verwendet wird. Dies ist zwar weniger häufig als Änderungen an Ihrem eigenen Code, hat aber größere Auswirkungen und es ist nützlich, dies zu messen.
  • Layoutänderungen zur Simulation einer Iteration von UI-Arbeiten
  • Stringbearbeitungen, um den Umgang mit Übersetzungsarbeiten zu simulieren.
  • Bereinigen Sie Builds, um Änderungen am Build selbst zu simulieren (z.B. Android Gradle-Plug-in-Update, Gradle-Update oder Bearbeitungen Ihres eigenen Build-Codes unter buildSrc).

Zum Benchmarking dieser Anwendungsfälle können Sie ein Szenario erstellen, mit dem die gradle-profiler-Ausführung gefördert wird und entsprechende Änderungen auf Ihre Quellen angewendet werden. Im Folgenden finden Sie einige häufige Szenarien.

Profilerstellung für verschiedene Arbeitsspeicher-/CPU-Einstellungen

Sie können mehrere Szenarien erstellen, die unterschiedliche Werte für org.gradle.jvmargs verwenden, um Benchmarks für unterschiedliche Arbeitsspeicher- und CPU-Einstellungen zu erstellen. Sie können beispielsweise folgende Szenarien erstellen:

# <root-project>/scenarios.txt
clean_build_2gb_4workers {
    tasks = [":app:assembleDebug"]
    gradle-args = ["--max-workers=4"]
    jvm-args = ["-Xmx2048m"]
    cleanup-tasks = ["clean"]
}
clean_build_parallelGC {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-XX:+UseParallelGC"]
    cleanup-tasks = ["clean"]
}

clean_build_G1GC_4gb {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-Xmx4096m", "-XX:+UseG1GC"]
    cleanup-tasks = ["clean"]
}

Wenn Sie gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt ausführen, werden drei Szenarien ausgeführt und Sie können vergleichen, wie lange :app:assembleDebug für jede dieser Konfigurationen dauert.

Profile für verschiedene Gradle-Plug-in-Versionen erstellen

Wenn Sie herausfinden möchten, wie sich eine Änderung der Version des Gradle-Plug-ins auf Build-Zeiten auswirkt, erstellen Sie ein Szenario für das Benchmarking. Dies erfordert einige Vorbereitungen, um die Plug-in-Version aus dem Szenario einschleusen zu können. Ändern Sie den build.gradle-Stamm des Stammverzeichnisses:

# <root-project>/build.gradle
buildscript {
    def agpVersion = providers.systemProperty("agpVersion").forUseAtConfigurationTime().orNull ?: '4.1.0'

    ext.kotlin = providers.systemProperty('kotlinVersion').forUseAtConfigurationTime().orNull ?: '1.4.0'

    dependencies {
        classpath "com.android.tools.build:gradle:$agpVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
    }
}

Jetzt können Sie die Versionen des Android-Gradle-Plug-ins und des Kotlin-Gradle-Plug-ins aus der Szenariodatei angeben und den Quelldateien durch das Szenario eine neue Methode hinzufügen:

# <root-project>/scenarios.txt
non_abi_change_agp4.1.0_kotlin1.4.10 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.1.0"
      "kotlinVersion" = "1.4.10"
}

non_abi_change_agp4.2.0_kotlin1.4.20 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.2.0-alpha16"
      "kotlinVersion" = "1.4.20"
}

Profil für inkrementellen Build erstellen

Die meisten Builds sind inkrementell, was dieses Szenario zu einem der wichtigsten Szenarien macht, für die Sie Profile erstellen müssen. Der Gradle-Profiler bietet umfassende Unterstützung für die Profilerstellung inkrementeller Builds. Änderungen können automatisch auf eine Quelldatei angewendet werden, indem der Methodentext geändert, eine neue Methode hinzugefügt oder ein Layout oder eine String-Ressource geändert wird. So können Sie beispielsweise inkrementelle Szenarien erstellen:

# <root-project>/scenarios.txt
non_abi_change {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

abi_change {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

layout_change {
    tasks = [":app:assembleDebug"]
    apply-android-layout-change-to = "app/src/main/res/your_layout_file.xml"
}
string_resource_change {
    tasks = [":app:assembleDebug"]
    apply-android-resource-value-change-to = "app/src/main/res/values/strings.xml"
}

Wenn Sie gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt ausführen, wird der HTML-Bericht mit den Benchmarking-Daten generiert.

Sie können inkrementelle Szenarien mit anderen Einstellungen wie Heap-Größe, Anzahl der Worker oder Gradle-Version kombinieren:

# <root-project>/scenarios.txt
non_abi_change_4g {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
}

non_abi_change_4g_8workers {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
    gradle-args = ["--max-workers=8"]
}

non_abi_change_3g_gradle67 {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx3072m"]
    version = ["6.7"]
}

Profil für einen sauberen Build erstellen

Zum Benchmarking eines sauberen Builds können Sie ein Szenario erstellen, mit dem die Ausführung des Gradle-Profilers ausgeführt wird:

# <root-project>/scenarios.txt
clean_build {
    tasks = [":app:assembleDebug"]
    cleanup-tasks = ["clean"]
}

Verwenden Sie den folgenden Befehl, um dieses Szenario auszuführen:

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt

Gradle-Option --profile verwenden

Führe die folgenden Schritte aus, um ein Build-Profil über die Gradle-Befehlszeile zu generieren und aufzurufen:

  1. Öffnen Sie ein Befehlszeilenterminal im Stammverzeichnis Ihres Projekts.
  2. Führen Sie einen sauberen Build durch, indem Sie den folgenden Befehl eingeben. Wenn Sie ein Profil für Ihren Build erstellen, sollten Sie zwischen jedem Build einen sauberen Build ausführen, da Gradle Aufgaben überspringt, wenn sich Eingaben für eine Aufgabe (z. B. Quellcode) nicht ändern. Daher wird ein zweiter Build ohne Eingabeänderungen immer schneller ausgeführt, da Aufgaben nicht noch einmal ausgeführt werden. Wenn Sie also die Aufgabe clean zwischen Ihren Builds ausführen, stellen Sie sicher, dass Sie ein Profil für den gesamten Build-Prozess erstellen.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Führen Sie einen Debug-Build für eine Ihrer Produktvarianten aus, z. B. die „dev“-Variante, mit den folgenden Flags:
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: Aktiviert die Profilerstellung.
    • --offline: Deaktiviert das Abrufen von Online-Abhängigkeiten durch Gradle. Dadurch wird sichergestellt, dass Verzögerungen, die durch den Versuch, die Abhängigkeiten von Gradle aktualisieren, die Profilerstellungsdaten nicht beeinträchtigen. Sie sollten das Projekt bereits einmal erstellt haben, um sicherzugehen, dass Gradle die Abhängigkeiten bereits heruntergeladen und im Cache gespeichert hat.
    • --rerun-tasks: Zwingt Gradle dazu, alle Aufgaben noch einmal auszuführen, und ignoriert alle Aufgabenoptimierungen.
  4. Abbildung 1: Projektansicht mit dem Speicherort der Profilberichte.

    Wechseln Sie nach Abschluss des Builds im Fenster Projekt zum Verzeichnis project-root/build/reports/profile/ (wie in Abbildung 1 dargestellt).

  5. Klicken Sie mit der rechten Maustaste auf die Datei profile-timestamp.html und wählen Sie Im Browser öffnen > Standard aus. Der Bericht sollte in etwa so aussehen wie in Abbildung 2. Sie können sich jeden Tab des Berichts ansehen, um mehr über Ihren Build zu erfahren. Auf dem Tab Aufgabenausführung sehen Sie beispielsweise, wie lange Gradle für die Ausführung der einzelnen Build-Aufgaben gedauert hat.

    Abbildung 2: Bericht in einem Browser ansehen

  6. Optional: Wiederholen Sie den Befehl in Schritt 3, bevor Sie Änderungen an Ihrem Projekt oder der Build-Konfiguration vornehmen, aber lassen Sie das Flag --rerun-tasks weg. Da Gradle versucht, Zeit zu sparen, indem Aufgaben, deren Eingaben sich nicht geändert haben (diese sind auf dem Tab Aufgabenausführung des Berichts als UP-TO-DATE gekennzeichnet sind, wie in Abbildung 3 dargestellt) nicht noch einmal ausführen, können Sie festlegen, welche Aufgaben Aufgaben ausführen, obwohl sie nicht geändert wurden. Wenn :app:processDevUniversalDebugManifest beispielsweise nicht als UP-TO-DATE gekennzeichnet ist, wird eventuell vorgeschlagen, das Manifest mit jedem Build dynamisch zu aktualisieren. Einige Aufgaben wie :app:checkDevDebugManifest müssen jedoch bei jedem Build ausgeführt werden.

    Abbildung 3: Ergebnisse der Aufgabenausführung ansehen.

Da Sie nun einen Build-Profilbericht haben, können Sie sich die Informationen auf den einzelnen Tabs des Berichts ansehen und damit nach Optimierungsmöglichkeiten suchen. Einige Build-Einstellungen erfordern Tests, da die Vorteile je nach Projekt und Workstation variieren können. Projekte mit einer großen Codebasis können beispielsweise vom Reduzieren des Codes profitieren, um nicht verwendeten Code zu entfernen und die Anwendungsgröße zu verkleinern. Kleinere Projekte profitieren jedoch möglicherweise stärker davon, die Codereduzierung vollständig zu deaktivieren. Außerdem kann das Erhöhen der Gradle-Heap-Größe (mithilfe von org.gradle.jvmargs) die Leistung auf Maschinen mit wenig Arbeitsspeicher beeinträchtigen.

Nachdem Sie Ihre Build-Konfiguration geändert haben, können Sie die Ergebnisse der Änderungen beobachten. Wiederholen Sie dazu die oben genannten Schritte und generieren Sie ein neues Build-Profil. Abbildung 4 zeigt beispielsweise einen Bericht für dieselbe Beispielanwendung, nachdem einige der auf dieser Seite beschriebenen grundlegenden Optimierungen angewendet wurden.

Abbildung 4: Aufrufen eines neuen Berichts nach Optimierung der Build-Geschwindigkeit