Gradle-Build – Übersicht

Android-Apps werden in der Regel mit dem Build-System Gradle erstellt. Bevor wir uns mit den Details der Konfiguration Ihres Builds befassen, sehen wir uns die Konzepte hinter dem Build an, damit Sie das System als Ganzes verstehen.

Was ist ein Build?

Ein Build-System wandelt Ihren Quellcode in eine ausführbare Anwendung um. Builds umfassen oft mehrere Tools, 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.

Tasks kapseln Befehle, die ihre Eingaben in Ausgaben übersetzen. 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 verknüpft. 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, aber andere Plug-ins sind dazu gedacht, Plug-ins zu erweitern. Das protobuf-Plug-in soll beispielsweise vorhandenen Plug-ins wie AGP oder java-library Protobuf-Unterstützung hinzufügen.

Gradle bevorzugt Konventionen gegenüber Konfigurationen. Plug-ins werden daher mit guten Standardwerten geliefert, aber Sie können den Build über eine deklarative domänenspezifische Sprache (DSL) weiter konfigurieren. Die DSL ist so konzipiert, dass Sie angeben können, was erstellt werden soll, und nicht, 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 Ihren Unterprojekten) angegeben.

Task-Eingaben können Dateien und Verzeichnisse sowie andere Informationen sein, die als Java-Typen (Integer, 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 miteinander verknüpft, dass eine vor der anderen ausgeführt werden muss.

Gradle unterstützt zwar das Schreiben von beliebigem Code und Taskdeklarationen in Ihren Build-Dateien, dies kann jedoch dazu führen, dass Tools Ihren Build schwerer verstehen und Sie ihn schwerer verwalten können. Sie können beispielsweise Tests für Code in Plug-ins schreiben, aber nicht in Build-Dateien. Stattdessen sollten Sie die Build-Logik und die Deklarationen von Aufgaben auf Plug-ins beschränken, die Sie oder jemand anderes definieren, 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 im Build enthalten sind. Außerdem werden Klassenpfade 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 deklarieren, von denen Plug-ins und Bibliotheken abgerufen werden sollen.
  • Konfiguration registriert Aufgaben für jedes Projekt und führt die Build-Datei aus, 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). Diese Grafik 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, überspringt Gradle sie.

Weitere Informationen finden Sie im Build-Lebenszyklus von Gradle.

Konfigurations-DSLs

Gradle verwendet eine domänenspezifische Sprache (DSL) zum Konfigurieren von Builds. Bei diesem deklarativen Ansatz geht es darum, Ihre Daten anzugeben, anstatt schrittweise (imperative) Anweisungen 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 Domainexperten als auch Programmierern, erleichtern, zu einem Projekt beizutragen, indem sie eine kleine Sprache definieren, die Daten auf natürlichere Weise darstellt. Gradle-Plug-ins können die DSL erweitern, um die Daten zu konfigurieren, die sie für ihre Aufgaben benötigen.

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

Der DSL-Code sieht im Hintergrund in etwa 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 ein Lambda zur Konfiguration verwendet, und durch ein Attribut mit demselben Namen, um darauf zuzugreifen. Dadurch wirkt der Code in Ihren Build-Dateien eher wie eine Datenspezifikation.

Externe Abhängigkeiten

Das Maven-Build-System hat ein System zur Spezifikation, Speicherung und Verwaltung von Abhängigkeiten eingeführt. Bibliotheken werden in Repositories (Servern oder Verzeichnissen) gespeichert, wobei Metadaten wie ihre Version und Abhängigkeiten von anderen Bibliotheken enthalten sind. Sie geben an, welche Repositories durchsucht werden sollen und welche Versionen der Abhängigkeiten Sie verwenden möchten. Das Build-System lädt sie dann während des Builds herunter.

Maven-Artefakte werden durch den Gruppennamen (Unternehmen, Entwickler usw.), den Artefaktnamen (Name der Bibliothek) und die Version des Artefakts identifiziert. Dies wird normalerweise als group:artifact:version dargestellt.

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

Sie können Ihr Projekt auch in Unterprojekte (in Android Studio auch als „Module“ bezeichnet) modularisieren, die ebenfalls als Abhängigkeiten verwendet werden können. Jedes Unterprojekt erzeugt Ausgaben (z. B. JAR-Dateien), die von Unterprojekten oder Ihrem Projekt auf oberster Ebene verwendet werden können. Dadurch kann die Build-Zeit verbessert werden, da isoliert wird, welche Teile neu erstellt werden müssen. Außerdem können die Verantwortlichkeiten in der Anwendung besser getrennt werden.

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

Build-Varianten

Wenn Sie eine Android-App 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 das 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.

Ein Release-Build optimiert die Anwendung, signiert sie mit Ihrem Releaseschlüssel und schützt die installierten Anwendungsdateien.

Mit Produktvarianten können Sie die enthaltenen Quell- und Abhängigkeitsvarianten für die Anwendung ändern. Beispielsweise kannst du „Demo“- und „Vollversion“-Varianten für deine App oder „kostenlose“ und „kostenpflichtige“ Varianten erstellen. Sie schreiben Ihre gemeinsame Quelle in ein „Haupt“-Source-Set-Verzeichnis und überschreiben oder fügen eine Quelle in einem Source-Set hinzu, das nach der Variante benannt ist.

Das AGP erstellt Varianten für jede Kombination aus Build-Typ und Produktvariante. Wenn Sie keine Varianten definieren, werden die Varianten nach den Build-Typen benannt. Wenn Sie beide definieren, heißt die Variante <flavor><Buildtype>. Beispiel: Bei den Build-Typen release und debug sowie den Varianten demo und full erstellt das AGP die folgenden Varianten:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Nächste Schritte

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