Zoptymalizuj szybkość kompilacji

Długi czas kompilacji spowalnia proces programowania. Na tej stronie znajdziesz metody, które pomogą Ci rozwiązać problemy z wąskimi gardłami szybkości kompilacji.

Ogólny proces zwiększania szybkości kompilacji aplikacji wygląda tak:

  1. Zoptymalizuj konfigurację kompilacji, wykonując kilka czynności, które natychmiast przynoszą korzyści większości projektów Android Studio.
  2. Profiluj kompilację, aby identyfikować i diagnozować niektóre trudniejsze wąskie gardła, które mogą być charakterystyczne dla Twojego projektu lub stacji roboczej.

Podczas tworzenia aplikacji w miarę możliwości wdróż ją na urządzeniu z Androidem 7.0 (poziom interfejsu API 24) lub nowszym. Nowsze wersje platformy Androida mają lepsze mechanizmy przekazywania aktualizacji aplikacji, np. środowisko wykonawcze Androida (ART) i natywna obsługa wielu plików DEX.

Uwaga: po pierwszej oczyszczonej kompilacji możesz zauważyć, że kolejne kompilacje (zarówno czyste, jak i przyrostowe) działają znacznie szybciej nawet bez stosowania żadnych optymalizacji opisanych na tej stronie. Wynika to z tego, że demon Gradle ma okres „rozgrzewki” zwiększający wydajność, podobnie jak w przypadku innych procesów JVM.

Zoptymalizuj konfigurację kompilacji

Postępuj zgodnie z tymi wskazówkami, aby zwiększyć szybkość kompilacji projektu w Android Studio.

Zadbaj o aktualność narzędzi

Narzędzia na Androida otrzymują optymalizacje kompilacji i nowe funkcje niemal po każdej aktualizacji. Niektóre wskazówki na tej stronie zakładają, że używasz najnowszej wersji. Aby korzystać z najnowszych optymalizacji, pamiętaj o aktualizowaniu tych informacji:

Użyj KSP zamiast kapt

Narzędzie do przetwarzania adnotacji Kotlin (kapt) jest znacznie wolniejsze niż procesor Kotlin (KSP). Jeśli piszesz źródło Kotlin z adnotacjami i używasz narzędzi przetwarzających adnotacje (np. Room), które obsługują KSP, warto przejść na KSP.

Unikanie kompilowania niepotrzebnych zasobów

Unikaj kompilowania i pakowania zasobów, których nie testujesz, takich jak dodatkowe wersje językowe czy zasoby dotyczące gęstości ekranu. Zamiast tego określ tylko 1 zasób językowy i 1 gęstość ekranu dla typu „dev”, jak pokazano w tym przykładzie:

Odlotowy

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")
        }
        ...
    }
}

Eksperymentuj z umieszczeniem portalu wtyczek Gradle na końcu

Na urządzeniu z Androidem wszystkie wtyczki znajdują się w repozytoriach google() i mavenCentral(). Kompilacja może jednak wymagać wtyczek innych firm, które zostaną rozwiązane za pomocą usługi gradlePluginPortal().

Gradle przeszukuje repozytoria w kolejności, w której zostały zadeklarowane, więc wydajność kompilacji poprawia się, jeśli wymienione jako pierwsze repozytoria zawierają większość wtyczek. Dlatego poeksperymentuj z wpisem gradlePluginPortal(), umieszczając go na końcu w bloku repozytorium w pliku settings.gradle. W większości przypadków pozwala to zminimalizować liczbę nadmiarowych wyszukiwań przy użyciu wtyczki i zwiększyć szybkość kompilacji.

Więcej informacji o tym, jak Gradle porusza się po wielu repozytoriach, znajdziesz w sekcji Deklarowanie wielu repozytoriów w dokumentacji Gradle.

Używanie statycznych wartości konfiguracji kompilacji w kompilacji do debugowania

Zawsze używaj wartości statycznych w przypadku właściwości, które znajdują się w pliku manifestu lub plikach zasobów w przypadku typu kompilacji do debugowania.

Używanie dynamicznych kodów wersji, nazw wersji, zasobów lub dowolnej innej logiki kompilacji, która zmienia plik manifestu, wymaga kompilacji pełnej aplikacji za każdym razem, gdy chcesz wprowadzić zmianę, nawet jeśli rzeczywista zmiana może wymagać jedynie wymiany „na gorąco”. Jeśli Twoja konfiguracja kompilacji wymaga takich właściwości dynamicznych, oddziel je od wariantów kompilacji wersji i zachowaj statyczne wartości kompilacji do debugowania, jak pokazano w tym przykładzie:

  ...
  // 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")
    }
  }

Zapoznaj się z przepisem setVersionsFromTask na GitHubie, aby dowiedzieć się, jak ustawić kod wersji dynamicznej w projekcie.

Używaj statycznych wersji zależności

Podczas zadeklarowania zależności w plikach build.gradle unikaj używania dynamicznych numerów wersji (takich ze znakiem plusa na końcu, np. 'com.android.tools.build:gradle:2.+'). Używanie dynamicznych numerów wersji może powodować nieoczekiwane aktualizacje wersji, trudności w rozwiązywaniu różnic wersji i wolniejsze kompilacje spowodowane sprawdzaniem dostępności aktualizacji przez Gradle. Zamiast nich używaj statycznych numerów wersji.

Tworzenie modułów biblioteki

Poszukaj w aplikacji kodu, który możesz przekonwertować na moduł biblioteki na Androidzie. Dzięki modularyzacji kodu w ten sposób system kompilacji będzie kompilować tylko zmodyfikowane moduły i buforować te dane wyjściowe na potrzeby przyszłych kompilacji. Modularyzacja zwiększa też skuteczność realizacji równoległego projektu, gdy włączysz tę optymalizację.

Tworzenie zadań na potrzeby niestandardowej logiki kompilacji

Jeśli po utworzeniu profilu kompilacji pokazuje on, że stosunkowo długa część czasu kompilacji upłynęła na etapie **konfigurowania projektów**, sprawdź skrypty build.gradle i poszukaj kodu do uwzględnienia w niestandardowym zadaniu Gradle. Przeniesienie pewnej logiki kompilacji do zadania sprawi, że zadanie będzie działać tylko wtedy, gdy jest to wymagane, wyniki będą zapisywane w pamięci podręcznej na potrzeby kolejnych kompilacji, a logika kompilacji będzie mogła działać równolegle, jeśli włączysz równoległe wykonywanie projektu. Więcej informacji o taksach dla niestandardowego modelu kompilacji znajdziesz w oficjalnej dokumentacji Gradle.

Wskazówka: jeśli kompilacja zawiera dużą liczbę zadań niestandardowych, możesz uporządkować pliki build.gradle przez utworzenie niestandardowych klas zadań. Dodaj klasy do katalogu project-root/buildSrc/src/main/groovy/. Gradle automatycznie umieszcza te klasy w ścieżce klasy we wszystkich plikach build.gradle w projekcie.

Konwertuj obrazy na WebP

WebP to format pliku graficznego, który oferuje kompresję stratną (np. JPEG) i przezroczystość (np. PNG). WebP zapewnia lepszą kompresję niż JPEG i PNG.

Zmniejszenie rozmiaru plików obrazów bez konieczności kompresowania w czasie kompilacji może przyspieszyć kompilację, zwłaszcza jeśli aplikacja wykorzystuje dużo zasobów obrazów. Podczas dekompresowania obrazów WebP możesz jednak zauważyć niewielki wzrost wykorzystania procesora przez urządzenie. Użyj Android Studio, aby łatwo przekonwertować obrazy do formatu WebP.

Wyłącz przechwytywanie PNG

Jeśli nie przekonwertujesz obrazów PNG na WebP, nadal możesz przyspieszyć kompresję, wyłączając automatyczną kompresję obrazów za każdym razem, gdy tworzysz aplikację.

Jeśli korzystasz z wtyczki do Androida do obsługi Gradle w wersji 3.0.0 lub nowszej, funkcja zapisywania plików PNG jest domyślnie wyłączona dla typu kompilacji „debuguj”. Aby wyłączyć tę optymalizację w przypadku innych typów kompilacji, dodaj do pliku build.gradle ten fragment:

Odlotowy

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

Ponieważ typy kompilacji i smaki usług nie definiują tej właściwości, podczas tworzenia wersji produkcyjnej aplikacji musisz ręcznie ustawić tę właściwość na true.

Eksperyment z równoległym kolektorem czyszczenia pamięci JVM

Wydajność kompilacji można zwiększyć, konfigurując optymalny kolektor czyszczenia pamięci JVM używany przez Gradle. Choć pakiet JDK 8 jest skonfigurowany tak, aby domyślnie używał równoległego kolektora śmieci, wersje JDK 9 i nowsze są skonfigurowane tak, by korzystały z kolektora śmieci G1.

Aby potencjalnie zwiększyć wydajność kompilacji, zalecamy przetestowanie kompilacji Gradle przy użyciu równoległego kolektora śmieci. W gradle.properties ustaw te opcje:

org.gradle.jvmargs=-XX:+UseParallelGC

Jeśli w tym polu znajdują się już inne opcje, dodaj nową opcję:

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

Aby zmierzyć szybkość kompilacji w różnych konfiguracjach, przeczytaj o profilowaniu kompilacji.

Zwiększ rozmiar sterty JVM

Jeśli zauważysz powolne kompilacje, a w szczególności, że zbieranie pamięci w wynikach Analizatora kompilacji zajmuje ponad 15% czasu kompilacji, zwiększ rozmiar sterty maszyny wirtualnej Java (JVM). W pliku gradle.properties ustaw limit na 4, 6 lub 8 GB, jak w tym przykładzie:

org.gradle.jvmargs=-Xmx6g

Następnie sprawdź, czy udało się zwiększyć szybkość kompilacji. Najłatwiejszym sposobem określenia optymalnej wielkości stosu jest niewielkie zwiększenie limitu o niewielkie zwiększenie, a następnie przetestowanie pod kątem wystarczającego przyspieszenia kompilacji.

Jeśli używasz też równoległego modułu do czyszczenia pamięci JVM, cały wiersz powinien wyglądać tak:

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

Błędy pamięci JVM możesz przeanalizować, włączając flagę HeapDumpOnOutOfMemoryError. Dzięki temu w przypadku wyczerpania pamięci JVM wygeneruje zrzut stosu.

Używanie nieprzechodnich klas R

Aby szybciej tworzyć kompilacje aplikacji z wieloma modułami, używaj nieprzechodnich klas R. Pomaga to zapobiegać duplikowaniu zasobów, ponieważ klasa R każdego modułu zawiera odwołania tylko do własnych zasobów bez pobierania odwołań z zależności. Pozwala to szybciej tworzyć kompilacje i osiągać korzyści płynące z unikania kompilacji. Jest to domyślne działanie wtyczki Androida do obsługi Gradle w wersji 8.0.0 i nowszych.

Od wersji Android Studio Bumblebee klasy nieprzejściowe R są domyślnie włączone w nowych projektach. W przypadku projektów utworzonych przy użyciu wcześniejszych wersji Androida Studio zaktualizuj je tak, aby używały nieprzechodnich klas R. W tym celu kliknij Refaktoryzacja > Przenieś do nieprzenośnych klas R.

Więcej informacji o zasobach aplikacji i klasie R znajdziesz w artykule o zasobach aplikacji.

Używanie niestałych klas R

Używaj w aplikacjach i testach niestałych pól klasy R, aby zwiększyć przyrost wartości kompilacji Java i umożliwić bardziej precyzyjne zmniejszanie zasobów. Pola klas R nie zawsze są stałe w przypadku bibliotek, ponieważ zasoby są ponumerowane podczas pakowania pakietu APK aplikacji lub testu, który zależy od tej biblioteki. Jest to domyślne działanie wtyczki Androida do obsługi Gradle w wersji 8.0.0 i nowszych.

Wyłącz flagę Jetifier

Większość projektów korzysta bezpośrednio z bibliotek AndroidX, dlatego możesz usunąć flagę Jetifier, aby zwiększyć wydajność kompilacji. Aby usunąć flagę Jetifier, ustaw android.enableJetifier=false w pliku gradle.properties.

Analizator kompilacji może sprawdzić, czy można bezpiecznie usunąć flagę. Pozwoli to zwiększyć wydajność projektu i przenieść go z niezarządzanych bibliotek pomocy Androida. Więcej informacji o Analizatorze kompilacji znajdziesz w artykule Rozwiązywanie problemów z wydajnością kompilacji.

Użyj pamięci podręcznej konfiguracji

Pamięć podręczna konfiguracji umożliwia Gradle rejestrowanie informacji o wykresie zadań kompilacji i wykorzystywanie ich w kolejnych kompilacjach, dzięki czemu Gradle nie musi ponownie konfigurować całej kompilacji.

Aby włączyć pamięć podręczną konfiguracji, wykonaj te czynności:

  1. Sprawdź, czy wszystkie wtyczki projektu są zgodne.

    Użyj Analizatora kompilacji, aby sprawdzić, czy Twój projekt jest zgodny z pamięcią podręczną konfiguracji. Analizator kompilacji uruchamia sekwencję kompilacji testowych, aby określić, czy można włączyć tę funkcję w projekcie. Listę obsługiwanych wtyczek znajdziesz w problemie nr 13490.

  2. Dodaj do pliku gradle.properties ten kod:

      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

Gdy pamięć podręczna konfiguracji jest włączona, przy pierwszym uruchomieniu projektu dane wyjściowe kompilacji to Calculating task graph as no configuration cache is available for tasks. Podczas kolejnych uruchomień w danych wyjściowych kompilacji będzie widoczny komunikat Reusing configuration cache.

Więcej informacji o pamięci podręcznej konfiguracji znajdziesz w poście na blogu na temat szczegółów dotyczących buforowania konfiguracji i w dokumentacji Gradle dotyczącej pamięci podręcznej konfiguracji.

Problemy z pamięcią podręczną konfiguracji wprowadzone w Gradle 8.1 i wtyczce Androida do obsługi Gradle 8.1

Pamięć podręczna konfiguracji stała się stabilna w Gradle 8.1 i wprowadzono śledzenie przez interfejsy API plików. Wywołania takie jak File.exists(), File.isDirectory() i File.list() są rejestrowane przez Gradle na potrzeby śledzenia plików wejściowych konfiguracji.

Wtyczka Androida do obsługi Gradle (AGP) 8.1 używa tych interfejsów API File w niektórych plikach, których Gradle nie powinna traktować jako danych wejściowych pamięci podręcznej. Powoduje to dodatkowe unieważnienie pamięci podręcznej, gdy jest używane z Gradle w wersji 8.1 lub nowszej, co spowalnia kompilację. W AGP 8.1 jako dane wejściowe pamięci podręcznej są traktowane te dane:

Wprowadź tekst Śledzenie problemów Naprawiono w
$GRADLE_USER_HOME/android/FakeDependency.jar Numer 289232054 8.2 AGP
dane wyjściowe cmake Numer 287676077 8.2 AGP
$GRADLE_USER_HOME/.android/analytics.settings Numer 278767328 8.3 AGP

Jeśli używasz tych interfejsów API lub wtyczki, które z nich korzystają, może wystąpić pogorszenie czasu kompilacji, ponieważ niektóre logiki kompilacji korzystające z tych interfejsów API mogą spowodować dodatkowe unieważnienie pamięci podręcznej. Na stronie o ulepszeniach w śledzeniu danych wejściowych konfiguracji kompilacji znajdziesz omówienie tych wzorców i sposobów rozwiązywania problemów logicznych kompilacji lub tymczasowe wyłączenie śledzenia przez interfejs API plików.