Omówienie kompilacji Gradle

Aplikacje na Androida są zwykle tworzone za pomocą systemu kompilacji Gradle. Zanim przejdziemy do szczegółów konfiguracji kompilacji, omówimy koncepcje, które za nią stoją, abyś mógł zobaczyć system jako całość.

Co to jest kompilacja?

System kompilacji przekształca kod źródłowy w aplikację wykonywalną. Procesy kompilacji 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 wyjściowe. Wtyczki określają zadania i ich konfigurację. Zastosowanie wtyczki do kompilacji rejestruje jej zadania i łączy je 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 tworzenie pliku JAR z kodu źródłowego Java. Podobne wtyczki istnieją w przypadku języka Kotlin i innych języków, ale inne wtyczki mają na celu rozszerzanie wtyczek. Na przykład wtyczka protobuf ma na celu dodanie obsługi protokołu protobuf do istniejących wtyczek, takich jak AGP czy java-library.

Gradle preferuje konwencję od konfiguracji, więc wtyczki będą miały dobre wartości domyślne od razu po zainstalowaniu, ale możesz dodatkowo skonfigurować kompilację za pomocą deklaratywnego języka DSL. Język DSL został zaprojektowany tak, aby można było określić co ma zostać utworzone, a nie jak to zrobić. Logika wtyczek zarządza „sposobem” działania. Ta konfiguracja jest określona w kilku plikach kompilacjiprojekcie (i podprojektach).

Dane wejściowe zadania mogą być plikami i katalogami, a także innymi informacjami zakodowanymi jako typy Java (liczby całkowite, ciągi znaków lub klasy niestandardowe). Dane wyjściowe mogą być tylko katalogami lub plikami, ponieważ muszą być zapisywane na dysku. Połączenie danych wyjściowych zadania z danymi wejściowymi innego zadania powoduje powiązanie tych zadań, dzięki czemu jedno musi zostać wykonane przed drugim.

Gradle umożliwia pisanie dowolnego kodu i deklaracji zadań w plikach kompilacji, ale może to utrudniać narzędziom analizowanie kompilacji, a Tobie jej utrzymywanie. Możesz na przykład pisać testy kodu w wtyczkach, ale nie w plikach kompilacji. Zamiast tego ogranicz logikę kompilacji i deklaracje zadań do wtyczek (zdefiniowanych przez Ciebie lub inną osobę) i zadeklaruj, jak chcesz używać tej logiki w plikach kompilacji.

Co się dzieje podczas działania kompilacji Gradle?

Kompilacje Gradle przebiegają w 3 fazach. Każda z tych faz wykonuje różne części kodu zdefiniowane 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. Ten etap 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. Warto pamiętać, że kod konfiguracji nie będzie mieć dostępu do danych ani plików wygenerowanych podczas wykonywania.
  • Wykonanie to faktyczne „budowanie” aplikacji. Wynikiem konfiguracji jest skierowany graf acykliczny (DAG) zadań, który reprezentuje wszystkie wymagane kroki kompilacji, o które poprosił użytkownik (zadania podane w wierszu poleceń lub jako wartości domyślne w plikach kompilacji). Ten graf przedstawia relacje między zadaniami, które są wyraźnie zadeklarowane 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 zadaniu. W tej fazie przestarzałe zadania są wykonywane w kolejności zdefiniowanej w grafie. Jeśli dane wejściowe zadania nie zmieniły się od czasu jego ostatniego wykonania, Gradle pominie to zadanie.

Więcej informacji znajdziesz w artykule Cykl życia kompilacji w dokumentacji Gradle.

Konfiguracyjne języki DSL

Gradle używa języka DSL (Domain-Specific Language) do konfigurowania kompilacji. To podejście deklaratywne polega na określeniu danych zamiast pisania instrukcji krok po kroku (imperatywnych). Pliki kompilacji możesz pisać w języku Kotlin lub Groovy, ale zdecydowanie zalecamy używanie języka Kotlin.

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

Na przykład skonfigurowanie 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)
        }
        // ...
    }
}

Groovy

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

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

W tle kod DSL jest podobny do tego:

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 DSL jest reprezentowany przez funkcję, która przyjmuje lambdę do skonfigurowania bloku, oraz właściwość o tej samej nazwie, która umożliwia dostęp do bloku. Dzięki temu kod w plikach kompilacji bardziej przypomina specyfikację danych.

Zależności zewnętrzne

System kompilacji Maven wprowadził specyfikację zależności, system przechowywania i zarządzania. Biblioteki są przechowywane w repozytoriach (serwerach lub katalogach) wraz z metadanymi, w tym wersją i zależnościami od innych bibliotek. Określasz, w których repozytoriach chcesz wyszukiwać, jakich wersji zależności chcesz używać, a system kompilacji pobiera je podczas kompilacji.

Artefakty Maven są identyfikowane przez nazwę grupy (firmy, dewelopera itp.), nazwę artefaktu (nazwę biblioteki) i wersję tego artefaktu. Zwykle jest to oznaczane jako group:artifact:version.

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

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

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

Warianty kompilacji

Podczas tworzenia aplikacji na Androida zwykle chcesz skompilować kilka wariantów. Warianty zawierają inny kod lub są tworzone z użyciem różnych opcji. Składają się z rodzajów kompilacji i wersji produktu.

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

Wersja debugowania nie minimalizuje ani nie zaciemnia aplikacji, co przyspiesza jej kompilację i zachowuje wszystkie symbole w niezmienionej postaci. Oznacza też aplikację jako „możliwą do debugowania”, podpisując ją ogólnym kluczem debugowania i umożliwiając dostęp do zainstalowanych plików aplikacji na urządzeniu. Umożliwia to przeglądanie zapisanych danych w plikach i bazach danych podczas działania aplikacji.

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

Korzystając z wersji produktu, możesz zmienić dołączone źródło i warianty zależności aplikacji. Możesz na przykład utworzyć wersje „demo” i „pełną” aplikacji lub wersje „bezpłatną” i „płatną”. Wspólne źródło zapisujesz w katalogu „głównego” zestawu źródeł, a źródło zastępujesz lub dodajesz w zestawie źródeł o nazwie odpowiadającej wersji.

AGP tworzy warianty dla każdej kombinacji typu kompilacji i wersji produktu. Jeśli nie zdefiniujesz wersji, warianty będą nazwane zgodnie z typami kompilacji. Jeśli zdefiniujesz obie opcje, wariant będzie nosił nazwę <flavor><Buildtype>. Na przykład w przypadku typów kompilacji releasedebug oraz wersji demofull AGP utworzy te warianty:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Dalsze kroki

Znasz już koncepcje kompilacji, więc przyjrzyj się strukturze kompilacji Androida w projekcie.