Gradle-Build – Übersicht

Android-Anwendungen werden in der Regel mit dem Gradle-Buildsystem erstellt. Bevor wir uns mit den Details der Build-Konfiguration befassen, sehen wir uns die Konzepte an, die dem Build zugrunde liegen, damit Sie das System als Ganzes verstehen.

Was ist ein Build?

Ein Build-System wandelt Ihren Quellcode in eine ausführbare Anwendung um. Für Builds sind oft mehrere Tools erforderlich, um Ihre Anwendung oder Bibliothek zu analysieren, zu kompilieren, zu verknüpfen und zu verpacken. Gradle verwendet einen aufgabenbasierten Ansatz, um diese Befehle zu organisieren und auszuführen.

Aufgaben kapseln Befehle, die ihre Eingaben in Ausgaben umwandeln. Plug-ins definieren Aufgaben und ihre Konfiguration. Wenn Sie ein Plug-in auf Ihren Build anwenden, werden seine Aufgaben registriert und über ihre Ein- und Ausgaben miteinander verbunden. Wenn Sie beispielsweise das Android-Gradle-Plug-in (AGP) auf Ihre Build-Datei anwenden, werden alle Aufgaben registriert, die zum Erstellen eines APK oder einer Android-Bibliothek erforderlich sind. Mit dem java-library-Plug-in können Sie eine JAR-Datei aus Java-Quellcode erstellen. Ähnliche Plug-ins gibt es für Kotlin und andere Sprachen. Andere Plug-ins sind jedoch dazu gedacht, Plug-ins zu erweitern. Das protobuf-Plug-in soll beispielsweise bestehenden Plug-ins wie AGP oder java-library Unterstützung für Protobuf hinzufügen.

Gradle bevorzugt Konventionen gegenüber Konfigurationen. Daher werden Plug-ins mit guten Standardwerten ausgeliefert. Sie können den Build jedoch über eine deklarative Domain-Specific Language (DSL) weiter konfigurieren. Die DSL ist so konzipiert, dass Sie angeben können, was erstellt werden soll, anstatt wie es erstellt werden soll. Die Logik in den Plug‑ins verwaltet das „Wie“. Diese Konfiguration wird in mehreren Build-Dateien in Ihrem Projekt (und Unterprojekten) angegeben.

Aufgabeneingaben können Dateien und Verzeichnisse sowie andere Informationen sein, die als Java-Typen (Ganzzahl, Strings oder benutzerdefinierte Klassen) codiert sind. Ausgaben können nur Verzeichnisse oder Dateien sein, da sie auf die Festplatte geschrieben werden müssen. Wenn Sie die Ausgabe einer Aufgabe mit der Eingabe einer anderen Aufgabe verbinden, werden die Aufgaben so verknüpft, dass eine vor der anderen ausgeführt werden muss.

Gradle unterstützt zwar das Schreiben von beliebigem Code und Task-Deklarationen in Ihren Build-Dateien, dies kann es jedoch für Tools schwieriger machen, Ihren Build zu verstehen, und für Sie, ihn zu verwalten. Sie können beispielsweise Tests für Code in Plug-ins, aber nicht in Build-Dateien schreiben. Stattdessen sollten Sie Build-Logik und Task-Deklarationen auf Plugins beschränken, die Sie oder jemand anderes definiert, und in Ihren Build-Dateien deklarieren, wie Sie diese Logik verwenden möchten.

Was passiert, wenn ein Gradle-Build ausgeführt wird?

Gradle-Builds werden in drei Phasen ausgeführt. In jeder dieser Phasen werden verschiedene Teile des Codes ausgeführt, die Sie in Ihren Build-Dateien definieren.

  • Bei der Initialisierung wird festgelegt, welche Projekte und Unterprojekte in den Build einbezogen werden, und es werden Classpaths mit Ihren Build-Dateien und angewendeten Plug-ins eingerichtet. In dieser Phase geht es um eine Einstellungsdatei, in der Sie die zu erstellenden Projekte und die Speicherorte angeben, von denen Plug-ins und Bibliotheken abgerufen werden sollen.
  • Bei der Konfiguration werden Aufgaben für jedes Projekt registriert und die Build-Datei wird ausgeführt, um die Build-Spezifikation des Nutzers anzuwenden. Es ist wichtig zu wissen, dass Ihr Konfigurationscode keinen Zugriff auf Daten oder Dateien hat, die während der Ausführung erstellt werden.
  • Bei der Ausführung wird die Anwendung tatsächlich erstellt. Die Ausgabe der Konfiguration ist ein gerichteter azyklischer Graph (Directed Acyclic Graph, DAG) von Aufgaben, der alle erforderlichen Build-Schritte darstellt, die vom Nutzer angefordert wurden (die Aufgaben, die in der Befehlszeile oder als Standardwerte in den Build-Dateien angegeben wurden). Dieses Diagramm stellt die Beziehung zwischen Aufgaben dar, entweder explizit in der Deklaration einer Aufgabe oder basierend auf ihren Ein- und Ausgaben. Wenn eine Aufgabe eine Eingabe hat, die die Ausgabe einer anderen Aufgabe ist, muss sie nach der anderen Aufgabe ausgeführt werden. In dieser Phase werden veraltete Aufgaben in der im Diagramm definierten Reihenfolge ausgeführt. Wenn sich die Eingaben einer Aufgabe seit der letzten Ausführung nicht geändert haben, wird sie von Gradle übersprungen.

Weitere Informationen finden Sie im Build-Lebenszyklus von Gradle.

Konfigurations-DSLs

Gradle verwendet eine domänenspezifische Sprache (Domain-Specific Language, DSL), um Builds zu konfigurieren. Bei diesem deklarativen Ansatz geht es darum, Ihre Daten anzugeben, anstatt Schritt-für-Schritt-Anleitungen (imperativ) zu schreiben. Sie können Ihre Build-Dateien mit Kotlin oder Groovy schreiben. Wir empfehlen jedoch dringend, Kotlin zu verwenden.

DSLs sollen es allen, sowohl Fachexperten als auch Programmierern, erleichtern, zu einem Projekt beizutragen. Dazu wird eine kleine Sprache definiert, die Daten auf natürlichere Weise darstellt. Gradle-Plug-ins können die DSL erweitern, um die Daten zu konfigurieren, die für ihre Aufgaben erforderlich sind.

Die Konfiguration des Android-Teils Ihres Builds könnte beispielsweise so aussehen:

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

Hinter den Kulissen sieht der DSL-Code so aus:

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

interface ApplicationExtension {
    var namespace: String?

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

    val defaultConfig: DefaultConfig

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

Jeder Block in der DSL wird durch eine Funktion dargestellt, die eine Lambda-Funktion zur Konfiguration und eine Property mit demselben Namen für den Zugriff darauf verwendet. Dadurch wirkt der Code in Ihren Build-Dateien eher wie eine Datenspezifikation.

Externe Abhängigkeiten

Das Maven-Buildsystem führte eine Spezifikation, ein Speicher- und ein Verwaltungssystem für Abhängigkeiten ein. Bibliotheken werden in Repositories (Server oder Verzeichnisse) gespeichert. Die Metadaten umfassen unter anderem die Version und Abhängigkeiten von anderen Bibliotheken. Sie geben an, in welchen Repositories gesucht werden soll, welche Versionen der Abhängigkeiten Sie verwenden möchten und dass das Build-System sie während des Builds herunterlädt.

Maven-Artefakte werden anhand des Gruppennamens (Unternehmen, Entwickler usw.), des Artefaktnamens (Name der Bibliothek) und der Version des Artefakts identifiziert. Dies wird in der Regel als group:artifact:version dargestellt.

Dieser Ansatz verbessert die Build-Verwaltung erheblich. Solche Repositories werden oft als „Maven-Repositories“ bezeichnet. Es geht jedoch nur darum, wie die Artefakte verpackt und veröffentlicht werden. Diese Repositories und Metadaten wurden in mehreren Build-Systemen wiederverwendet, darunter Gradle (und Gradle kann in diesen Repositories veröffentlichen). Öffentliche Repositories ermöglichen die Freigabe für alle, während Unternehmensrepositories interne Abhängigkeiten im Unternehmen behalten.

Sie können Ihr Projekt auch modularisieren, indem Sie es in Unterprojekte (in Android Studio auch als „Module“ bezeichnet) aufteilen, die ebenfalls als Abhängigkeiten verwendet werden können. Jedes Unterprojekt erzeugt Ausgaben (z. B. JARs), die von Unterprojekten oder Ihrem Projekt der obersten Ebene verwendet werden können. Dadurch kann die Build-Zeit verkürzt werden, da nur die Teile neu erstellt werden müssen, die sich geändert haben. Außerdem lassen sich die Verantwortlichkeiten in der Anwendung besser trennen.

Weitere Informationen zum Angeben von Abhängigkeiten finden Sie unter Build-Abhängigkeiten hinzufügen.

Build-Varianten

Wenn Sie eine Android-Anwendung erstellen, möchten Sie in der Regel mehrere Varianten erstellen. Varianten enthalten unterschiedlichen Code oder werden mit unterschiedlichen Optionen erstellt und bestehen aus Build-Typen und Produktvarianten.

Build-Typen variieren deklarierte Build-Optionen. Standardmäßig richtet AGP die Build-Typen „release“ und „debug“ ein. Sie können diese jedoch anpassen und weitere hinzufügen, z. B. für Staging oder interne Tests.

Bei einem Debug-Build wird Ihre Anwendung nicht minimiert oder verschleiert. Dadurch wird der Build beschleunigt und alle Symbole bleiben unverändert. Außerdem wird die Anwendung als „debugfähig“ gekennzeichnet, mit einem generischen Debug-Schlüssel signiert und der Zugriff auf die installierten Anwendungsdateien auf dem Gerät ermöglicht. So können Sie gespeicherte Daten in Dateien und Datenbanken untersuchen, während die Anwendung ausgeführt wird.

Bei einem Release-Build wird die Anwendung optimiert, mit Ihrem Releaseschlüssel signiert und die installierten Anwendungsdateien werden geschützt.

Mit Produktvarianten können Sie die enthaltenen Quell- und Abhängigkeitsvarianten für die Anwendung ändern. Sie können beispielsweise die Produktvarianten „Demo“ und „Vollversion“ oder „Kostenlos“ und „Kostenpflichtig“ für Ihre Anwendung erstellen. Sie schreiben den gemeinsamen Quellcode in ein „main“-Quellset-Verzeichnis und überschreiben oder fügen Quellcode in einem Quellset hinzu, das nach dem Flavor benannt ist.

AGP erstellt Varianten für jede Kombination aus Build-Typ und Produktvariante. Wenn Sie keine Varianten definieren, werden sie nach den Build-Typen benannt. Wenn Sie beides definieren, wird die Variante <flavor><Buildtype> genannt. Wenn Sie beispielsweise die Build-Typen release und debug sowie die Produktvarianten demo und full verwenden, erstellt AGP die folgenden Varianten:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Nächste Schritte

Nachdem Sie sich die Build-Konzepte angesehen haben, sehen Sie sich die Android-Build-Struktur in Ihrem Projekt an.