Omówienie kompilacji Gradle

Aplikacje na Androida są zwykle tworzone przy użyciu systemu kompilacji Gradle. Zanim przejdziemy do szczegółów konfigurowania kompilacji, omówimy koncepcje, na których się opiera, aby można było zobaczyć system jako całość.

Czym jest kompilacja?

System kompilacji przekształca kod źródłowy w aplikację wykonywalną. Kompilacje często obejmują wiele narzędzi do analizowania, kompilowania, łączenia i pakowania aplikacji lub biblioteki. Gradle używa podejścia opartego na zadaniach do porządkowania i uruchamiania tych poleceń.

Zadania zawierają polecenia, które przekształcają dane wejściowe w dane wyjściowe. Wtyczki definiują zadania i ich konfigurację. Zastosowanie wtyczki do kompilacji powoduje zarejestrowanie jej zadań i połączenie ich ze sobą za pomocą danych wejściowych i wyjściowych. Na przykład zastosowanie wtyczki Androida do obsługi Gradle (AGP) w pliku kompilacji spowoduje zarejestrowanie wszystkich zadań niezbędnych do utworzenia pliku APK lub biblioteki Androida. Wtyczka java-library umożliwia utworzenie pliku jar z kodu źródłowego Java. Podobne wtyczki istnieją dla Kotlina i innych języków, ale inne wtyczki mają na celu rozszerzenie wtyczek. Na przykład wtyczka protobuf ma dodawać obsługę protobuf do istniejących wtyczek, takich jak AGP lub java-library.

Gradle preferuje konwencję nad konfiguracją, więc wtyczki będą od razu zawierać dobre wartości domyślne, ale możesz dodatkowo skonfigurować kompilację za pomocą deklaratywnego języka dziedzinowego (DSL). Język DSL został zaprojektowany tak, aby można było określić, co ma zostać utworzone, a nie jak ma to zostać utworzone. Logika we wtyczkach zarządza „jak”. Ta konfiguracja jest określona w kilku plikach kompilacji w projekcie (i podprojektach).

Danymi wejściowymi zadania mogą być pliki i katalogi, a także inne informacje zakodowane jako typy Javy (liczby całkowite, ciągi znaków lub klasy niestandardowe). Dane wyjściowe mogą być tylko katalogiem lub plikami, ponieważ muszą zostać zapisane na dysku. Połączenie danych wyjściowych zadania z danymi wejściowymi innego zadania powoduje połączenie zadań, dzięki czemu jedno z nich musi zostać wykonane przed drugim.

Gradle obsługuje pisanie dowolnego kodu i deklaracji zadań w plikach kompilacji, ale może to utrudnić narzędziom zrozumienie kompilacji, a Tobie jej utrzymanie. Możesz na przykład pisać testy kodu we wtyczkach, ale nie w plikach kompilacji. Zamiast tego należy ograniczyć logikę kompilacji i deklaracje zadań do wtyczek (zdefiniowanych przez Ciebie lub kogoś innego) i zadeklarować, jak chcesz używać tej logiki w plikach kompilacji.

Co się dzieje podczas kompilacji Gradle?

Kompilacje Gradle są uruchamiane w 3 fazach. Każda z tych faz wykonuje różne części kodu, które definiujesz w plikach kompilacji.

  • Inicjowanie określa, które projekty i podprojekty są uwzględniane w kompilacji, oraz konfiguruje ścieżki klas zawierające pliki kompilacji i zastosowane wtyczki. Ta faza koncentruje się na pliku ustawień, w którym deklarujesz projekty do skompilowania oraz lokalizacje, z których mają być pobierane wtyczki i biblioteki.
  • Konfiguracja rejestruje zadania dla każdego projektu i wykonuje plik kompilacji, aby zastosować specyfikację kompilacji użytkownika. Ważne jest, aby pamiętać, że kod konfiguracji nie będzie miał dostępu do danych ani plików wygenerowanych podczas wykonywania.
  • Wykonywanie to rzeczywiste „kompilowanie” aplikacji. Wynikiem konfiguracji jest skierowany graf acykliczny (DAG) zadań, który reprezentuje wszystkie wymagane kroki kompilacji żądane przez użytkownika (zadania podane w wierszu poleceń lub jako domyślne w plikach kompilacji). Ten wykres przedstawia relacje między zadaniami, które są wyraźnie określone w deklaracji zadania lub oparte na jego danych wejściowych i wyjściowych. Jeśli zadanie ma dane wejściowe, które są danymi wyjściowymi innego zadania, musi zostać uruchomione po tym innym zadaniu. W tej fazie uruchamiane są nieaktualne zadania w kolejności określonej na wykresie. Jeśli dane wejściowe zadania nie zmieniły się od czasu jego ostatniego wykonania, Gradle je pominie.

Więcej informacji znajdziesz w artykule o cyklu życia kompilacji Gradle.

Konfiguracja DSL

Gradle używa języka dziedzinowego (DSL) do konfigurowania kompilacji. To podejście deklaratywne koncentruje się na określaniu danych, a nie na pisaniu szczegółowych instrukcji (imperatywnych). Pliki kompilacji możesz pisać w języku Kotlin lub Groovy, ale zdecydowanie zalecamy używanie Kotlina.

Języki DSL ułatwiają wszystkim, zarówno ekspertom w danej dziedzinie, jak i programistom, udział w projekcie, definiując mały język, który reprezentuje dane w bardziej naturalny sposób. Wtyczki Gradle mogą rozszerzać DSL, aby konfigurować dane potrzebne do wykonywania zadań.

Na przykład konfiguracja części kompilacji na Androida może wyglądać tak:

Kotlin

android {
    namespace = "com.example.app"
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

Dynamiczny

android {
    namespace = 'com.example.app'
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = 'com.example.app'
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

Za kulisami kod DSL wygląda mniej więcej tak:

fun Project.android(configure: ApplicationExtension.() -> Unit) {
    ...
}

interface ApplicationExtension {
    var namespace: String?

    fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
        ...
    }

    val defaultConfig: DefaultConfig

    fun defaultConfig(configure: DefaultConfig.() -> Unit) {
        ...
    }
}

Każdy blok w języku DSL jest reprezentowany przez funkcję, która przyjmuje lambdę do jego konfiguracji, oraz właściwość o tej samej nazwie, która umożliwia dostęp do niego. Dzięki temu kod w plikach kompilacji przypomina bardziej specyfikację danych.

Zależności zewnętrzne

System kompilacji Maven wprowadził specyfikację zależności, system pamięci masowej i system zarządzania. Biblioteki są przechowywane w repozytoriach (serwerach lub katalogach) wraz z metadanymi, w tym informacjami o wersji i zależnościach od innych bibliotek. Określasz, które repozytoria mają być przeszukiwane i które wersje zależności mają być używane, a system kompilacji pobiera je podczas kompilacji.

Artefakty Maven są identyfikowane według nazwy grupy (firmy, dewelopera itp.), nazwy artefaktu (nazwy biblioteki) i wersji artefaktu. Zwykle jest to group:artifact:version.

Takie podejście znacznie ułatwia zarządzanie kompilacjami. Takie repozytoria są często nazywane „repozytoriami Maven”, ale chodzi o sposób pakowania i publikowania artefaktów. Te repozytoria i metadane zostały ponownie wykorzystane w kilku systemach kompilacji, w tym w Gradle (a Gradle może publikować w tych repozytoriach). Repozytoria publiczne umożliwiają udostępnianie wszystkim, a repozytoria firmowe przechowują wewnętrzne zależności w firmie.

Możesz też podzielić projekt na moduły, czyli podprojekty (w Android Studio nazywane też „modułami”), które mogą być używane jako zależności. Każdy podprojekt generuje dane wyjściowe (np. pliki JAR), które mogą być wykorzystywane przez podprojekty lub projekt najwyższego poziomu. Może to skrócić czas kompilacji dzięki wyodrębnieniu części, które trzeba ponownie skompilować, a także lepiej rozdzielić obowiązki w aplikacji.

Więcej informacji o określaniu zależności znajdziesz w artykule Dodawanie zależności kompilacji.

Warianty kompilacji

Podczas tworzenia aplikacji na Androida zwykle chcesz utworzyć kilka wariantów. Warianty zawierają inny kod lub są tworzone z innymi opcjami i składają się z typów kompilacji i wariantów usługi.

Typy kompilacji różnią się zadeklarowanymi opcjami kompilacji. Domyślnie AGP konfiguruje typy kompilacji „release” i „debug”, ale możesz je dostosować i dodać więcej (np. do testów przejściowych lub wewnętrznych).

Kompilacja do debugowania nie minifikuje ani nie zaciemnia aplikacji, co przyspiesza jej kompilację i zachowuje wszystkie symbole w niezmienionej postaci. Oznacza też aplikację jako „z możliwością debugowania”, podpisując ją ogólnym kluczem debugowania i umożliwiając dostęp do zainstalowanych plików aplikacji na urządzeniu. Dzięki temu możesz przeglądać dane zapisane w plikach i bazach danych podczas działania aplikacji.

Kompilacja do publikacji optymalizuje aplikację, podpisuje ją kluczem wersji i chroni zainstalowane pliki aplikacji.

Za pomocą wariantów usługi możesz zmieniać dołączone źródła i warianty zależności aplikacji. Możesz na przykład utworzyć wersje „demo” i „pełną” lub „bezpłatną” i „płatną”. Wspólne źródło zapisujesz w katalogu „głównego” zbioru źródeł, a źródło zastępujesz lub dodajesz w zbiorze źródeł o nazwie odpowiadającej wersji.

AGP tworzy warianty dla każdej kombinacji typu kompilacji i wariantu usługi. Jeśli nie zdefiniujesz wersji, warianty będą miały nazwy typów kompilacji. Jeśli zdefiniujesz obie, wariant będzie miał nazwę <flavor><Buildtype>. Na przykład w przypadku typów kompilacji release i debug oraz wersji demo i full AGP utworzy warianty:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Dalsze kroki

Teraz, gdy znasz już pojęcia związane z kompilacją, przyjrzyj się strukturze kompilacji Androida w projekcie.