Profila la tua build

I progetti più grandi, o quelli che implementano molta logica di build personalizzata, potrebbero richiedere un'analisi più approfondita del processo di build per individuare i colli di bottiglia. Puoi farlo profilando il tempo impiegato da Gradle per eseguire ogni fase del ciclo di vita e ogni attività di build. Ad esempio, se il tuo profilo di build mostra che Gradle dedica troppo tempo a configurare il tuo progetto, potrebbe essere necessario rimuovere la logica di build personalizzata dalla fase di configurazione. Inoltre, se l'attività mergeDevDebugResources consuma gran parte del tempo di compilazione, potrebbe essere necessario convertire le immagini in WebP o disattivare il crunching PNG.

Se utilizzi Android Studio 4.0 o versioni successive, il modo migliore per esaminare i problemi di prestazioni della build è utilizzare lo strumento di analisi build.

Inoltre, sono disponibili due opzioni per la profilazione della build al di fuori di Android Studio:

  1. Lo strumento gradle-profiler in modalità autonoma, efficace per un'analisi approfondita della build.

  2. L'opzione Gradle --profile, un pratico strumento disponibile dalla riga di comando Gradle.

Utilizzo dello strumento autonomo gradle-profiler

Per trovare la configurazione del progetto che offre la migliore velocità di build, devi utilizzare Gradle Profiler, uno strumento per la raccolta di dati di profilazione e benchmarking per le build di Gradle. Il profiler Gradle consente di creare scenari di build ed eseguirli più volte, impedendo un'elevata varianza tra i risultati e garantendo la riproducibilità dei risultati.

La modalità di benchmarking deve essere utilizzata per raccogliere informazioni sulle build pulite e incrementali, mentre la modalità di profilazione può essere utilizzata per raccogliere informazioni più granulari sulle esecuzioni, inclusi gli snapshot della CPU.

Ecco alcune configurazioni di configurazione del progetto per il benchmarking:

  • Versioni plug-in
  • Versioni Gradle
  • Impostazioni JVM (dimensioni heap, dimensioni permgen, garbage collection e così via)
  • Numero di worker Gradle (org.gradle.workers.max)
  • Opzioni per plug-in per ottimizzare ulteriormente le prestazioni

Per iniziare

  • Installa gradle-profiler seguendo queste istruzioni
  • Esecuzione: gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug

Verrà eseguito il benchmark di una build completamente aggiornata perché --benchmark esegue l'attività più volte senza modificare il progetto intermedio. Verrà quindi generato un report HTML nella directory profile-out/ che mostra i tempi di compilazione.

Esistono altri scenari che potrebbero essere più utili per il benchmarking:

  • Modifiche al codice nel corpo di un metodo in una classe in cui svolgi la maggior parte del tuo lavoro.
  • Modifiche all'API in un modulo utilizzato in tutto il progetto. Sebbene sia meno frequente delle modifiche al codice, questa operazione ha un impatto maggiore ed è utile per misurarla.
  • Modifiche al layout per simulare l'iterazione del lavoro dell'interfaccia utente.
  • Modifiche di stringhe per simulare la gestione del lavoro di traduzione.
  • Pulisci le build per simulare le modifiche alla build (ad es. aggiornamento del plug-in Android Gradle, aggiornamento di Gradle o modifiche al codice build in buildSrc).

Per eseguire il benchmark di questi casi d'uso, puoi creare uno scenario che verrà utilizzato per promuovere l'esecuzione di gradle-profiler e che applichi le modifiche appropriate alle tue origini. Di seguito puoi esaminare alcuni degli scenari comuni.

Profilazione di impostazioni di memoria/CPU diverse

Per eseguire il confronto con impostazioni di memoria e CPU diverse, puoi creare più scenari che utilizzano valori diversi per org.gradle.jvmargs. Ad esempio, puoi creare scenari:

# <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"]
}

L'esecuzione di gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt eseguirà tre scenari e potrai confrontare il tempo necessario a :app:assembleDebug per ognuna di queste configurazioni.

Profilazione di versioni del plug-in Gradle diverse

Per scoprire in che modo la modifica della versione del plug-in Gradle influisce sui tempi di creazione, crea uno scenario per il benchmarking. Ciò richiede una certa preparazione per rendere la versione del plug-in iniettabile dallo scenario. Modifica il file build.gradle root:

# <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"
    }
}

Ora puoi specificare le versioni del plug-in Android per Gradle e del plug-in Kotlin Gradle dal file degli scenari e fare in modo che lo scenario aggiunga un nuovo metodo ai file di origine:

# <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"
}

Profilazione di una build incrementale

La maggior parte delle build è incrementale, il che rende questo uno degli scenari più importanti da profilare. Profiler Gradle offre un ampio supporto per la profilazione di build incrementali. È in grado di applicare automaticamente le modifiche a un file di origine modificando il corpo di un metodo, l'aggiunta di un nuovo metodo o la modifica di un layout o di una risorsa stringa. Ad esempio, puoi creare scenari incrementali come questo:

# <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"
}

L'esecuzione di gradle-profiler --benchmark --project-dir &lt;root-project> --scenario-file scenarios.txt genera il report HTML con i dati di benchmarking.

Puoi combinare scenari incrementali con altre impostazioni, ad esempio la dimensione dello heap, il numero di worker o la versione Gradle:

# <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"]
}

Profilazione di una build pulita

Per eseguire il benchmark di una build pulita, puoi creare uno scenario che verrà utilizzato per guidare l'esecuzione di gradle-profiler:

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

Per eseguire questo scenario, utilizza il comando seguente:

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

Utilizzo dell'opzione Gradle --profile

Per generare e visualizzare un profilo di build dalla riga di comando Gradle, segui questi passaggi:

  1. Apri un terminale a riga di comando nella directory principale del progetto.
  2. Esegui una build pulita inserendo il comando seguente. Quando profila la tua build, devi eseguire una build pulita tra una build e l'altra perché Gradle ignora le attività quando gli input relativi a un'attività (come il codice sorgente) non cambiano. Di conseguenza, una seconda build senza modifiche all'input viene sempre eseguita più velocemente perché le attività non vengono eseguite di nuovo. Di conseguenza, l'esecuzione dell'attività clean tra le build garantisce la profilazione dell'intero processo di compilazione.
    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. Esegui una build di debug di una delle versioni di prodotto, ad esempio la versione "dev", con i seguenti flag:
    gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    
    • --profile: abilita la profilazione.
    • --offline: disattiva Gradle dal recupero delle dipendenze online. In questo modo, tutti i ritardi causati da Gradle che tenta di aggiornare le dipendenze non interferiscono con i dati di profilazione. Dovresti aver già creato il progetto una volta per assicurarti che Gradle abbia già scaricato e memorizzato nella cache le dipendenze.
    • --rerun-tasks: obbliga Gradle a ripetere tutte le attività e ignora eventuali ottimizzazioni.
  4. Figura 1. Visualizzazione del progetto che indica la posizione dei report del profilo.

    Una volta completata la build, utilizza la finestra Progetto e vai alla directory project-root/build/reports/profile/ (come mostrato nella figura 1).

  5. Fai clic con il tasto destro del mouse sul file profile-timestamp.html e seleziona Apri nel browser > Predefinita. Il report dovrebbe avere un aspetto simile a quello mostrato nella figura 2. Puoi esaminare ciascuna scheda del report per saperne di più sulla build, ad esempio la scheda Esecuzione dell'attività che mostra il tempo impiegato da Gradle per eseguire ogni attività di build.

    Figura 2. Visualizzare un report in un browser.

  6. Facoltativo: prima di apportare modifiche alla configurazione del progetto o della build, ripeti il comando nel passaggio 3, ma ometti il flag --rerun-tasks. Poiché Gradle tenta di risparmiare tempo non rieseguindo attività i cui input non sono stati modificati (questi sono indicati come UP-TO-DATE nella scheda Esecuzione delle attività del report, come mostrato nella Figura 3), puoi identificare quali attività stanno eseguendo attività quando non dovrebbero esserlo. Ad esempio, se :app:processDevUniversalDebugManifest non è contrassegnato come UP-TO-DATE, potrebbe suggerire che la configurazione della build aggiorni in modo dinamico il manifest con ogni build. Tuttavia, alcune attività devono essere eseguite durante ogni build, ad esempio :app:checkDevDebugManifest.

    Figura 3. Visualizzazione dei risultati dell'esecuzione dell'attività.

Ora che hai creato un report sul profilo di build, puoi iniziare a cercare opportunità di ottimizzazione esaminando le informazioni in ogni scheda del report. Alcune impostazioni di build richiedono una sperimentazione perché i vantaggi possono variare tra progetti e workstation. Ad esempio, i progetti con un codebase di grandi dimensioni possono trarre vantaggio dalla riduzione del codice per rimuovere il codice inutilizzato e ridurre le dimensioni dell'app. Tuttavia, i progetti più piccoli possono trarre vantaggio dalla disattivazione della riduzione del codice. Inoltre, l'aumento della dimensione heap Gradle (utilizzando org.gradle.jvmargs) potrebbe influire negativamente sulle prestazioni sulle macchine con memoria ridotta.

Dopo aver apportato una modifica alla configurazione della build, osserva i risultati ripetendo i passaggi precedenti e generando un nuovo profilo di build. Ad esempio, la figura 4 mostra un report per la stessa app di esempio dopo aver applicato alcune delle ottimizzazioni di base descritte in questa pagina.

Figura 4. Visualizzazione di un nuovo report dopo aver ottimizzato la velocità di build.