Android-Anwendungen werden in der Regel mit dem Gradle-Buildsystem erstellt. Bevor wir uns mit den Details zur Konfiguration Ihres Builds befassen, sehen wir uns die Konzepte hinter dem Build an, damit Sie das System als Ganzes sehen können.
Was ist ein Build?
Ein Build-System wandelt Ihren Quellcode in eine ausführbare Anwendung um. Builds umfassen häufig mehrere Tools, mit denen Sie Ihre Anwendung oder Bibliothek analysieren, kompilieren, verknüpfen und verpacken können. Gradle verwendet einen aufgabenbasierten Ansatz, um diese Befehle zu organisieren und auszuführen.
Aufgaben umfassen Befehle, die Eingaben in Ergebnisse umwandeln. Plug-ins definieren Aufgaben und ihre Konfiguration. Wenn Sie ein Plug-in auf Ihren Build anwenden, werden dessen Aufgaben registriert und über die Ein- und Ausgaben 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 Plug-in java-library
können Sie eine JAR-Datei aus dem Java-Quellcode erstellen. Für Kotlin und andere Sprachen gibt es ähnliche Plug-ins, während andere Plug-ins zur Erweiterung von Plug-ins gedacht sind. Das Plug-in protobuf
dient beispielsweise dazu, vorhandenen Plug-ins wie AGP oder java-library
die protobuf-Unterstützung hinzuzufügen.
Gradle bevorzugt Konvention gegenüber Konfiguration, sodass Plug-ins gute Standardwerte liefern. Sie können den Build aber zusätzlich über eine deklarative domainspezifische Sprache (DSL) 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 steuert das „Wie“. Diese Konfiguration wird in mehreren Build-Dateien in Ihrem Projekt (und in den untergeordneten Projekten) angegeben.
Aufgabeneingaben können Dateien und Verzeichnisse sowie andere als Java-Typen codierte Informationen sein (Ganzzahl, Strings oder benutzerdefinierte Klassen). Ausgaben können nur Verzeichnisse oder Dateien sein, da sie auf der 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 beliebigen Codes und Aufgabendeklarationen in Ihren Build-Dateien, dies kann jedoch die Analyse Ihres Builds durch Tools erschweren und die Wartung für Sie erschweren. Sie können beispielsweise Tests für Code in Plug-ins schreiben, aber nicht in Build-Dateien. Stattdessen sollten Sie die Build-Logik und Aufgabendeklarationen auf Plug-ins beschränken, die von Ihnen oder jemand anderem definiert werden, und erklären, wie Sie diese Logik in Ihren Build-Dateien 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 Codeteile ausgeführt, die Sie in Ihren Build-Dateien definieren.
- Bei der Initialisierung wird festgelegt, welche Projekte und Unterprojekte in den Build eingeschlossen werden. Außerdem werden Classpfade mit Ihren Build-Dateien und angewendeten Plug-ins eingerichtet. In dieser Phase liegt der Schwerpunkt auf einer Konfigurationsdatei, in der Sie die zu erstellenden Projekte und die Speicherorte angeben, von denen aus Plugins und Bibliotheken abgerufen werden sollen.
- Configuration registriert Aufgaben für jedes Projekt und führt die Build-Datei aus, um die Build-Spezifikation des Nutzers anzuwenden. Ihr Konfigurationscode hat keinen Zugriff auf Daten oder Dateien, die während der Ausführung erstellt werden.
- Bei der Ausführung erfolgt die eigentliche „Erstellung“ der Anwendung. Die Ausgabe der Konfiguration ist ein gerichteter azyklischer Graph (Directed Acyclic Graph, DAG) von Aufgaben, der alle erforderlichen Buildschritte darstellt, die vom Nutzer angefordert wurden (die Aufgaben, die in der Befehlszeile oder als Standard in den Builddateien angegeben sind). Dieses Diagramm stellt die Beziehung zwischen Aufgaben dar, entweder explizit in der Deklaration einer Aufgabe oder basierend auf den 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 domainspezifische Sprache (DSL), um Builds zu konfigurieren. Bei diesem deklarativen Ansatz liegt der Schwerpunkt auf der Angabe Ihrer Daten, anstatt eine detaillierte (imperative) Anleitung zu schreiben. Sie können Ihre Build-Dateien mit Kotlin oder Groovy schreiben. Wir empfehlen jedoch dringend Kotlin.
DSLs sollen es allen Beteiligten, sowohl Fachleuten 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 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 = 34 // ... defaultConfig { applicationId = "com.example.app" minSdk = 34 // ... } }
Groovy
android { namespace 'com.example.myapplication' compileSdk 34 // ... defaultConfig { applicationId "com.example.myapplication" minSdk 24 // ... } }
Im Hintergrund sieht der DSL-Code in etwa so aus:
fun Project.android(configure: ApplicationExtension.() -> Unit) {
...
}
interface ApplicationExtension {
var compileSdk: Int
var namespace: String?
val defaultConfig: DefaultConfig
fun defaultConfig(configure: DefaultConfig.() -> Unit) {
...
}
}
Jeder Block in der DSL wird durch eine Funktion dargestellt, die zum Konfigurieren ein Lambda und zum Zugriff eine Property mit demselben Namen verwendet. Dadurch fühlt sich der Code in Ihren Build-Dateien eher wie eine Datenspezifikation an.
Externe Abhängigkeiten
Das Maven-Buildsystem führte ein Abhängigkeits-, Speicher- und Verwaltungssystem ein. Bibliotheken werden in Repositories (Servern oder Verzeichnissen) mit Metadaten gespeichert, einschließlich ihrer Version und Abhängigkeiten von anderen Bibliotheken. Sie geben an, in welchen Repositories gesucht werden soll, und welche Versionen der Abhängigkeiten verwendet werden sollen. Das Build-System lädt sie dann während des Builds herunter.
Maven-Artefakte werden anhand des Gruppennamens (Unternehmen, Entwickler usw.), des Artefaktnamens (Name der Bibliothek) und der Version dieses Artefakts identifiziert. Dies wird normalerweise als group:artifact:version
dargestellt.
Dieser Ansatz verbessert das Build-Management erheblich. Solche Repositories werden oft als "Maven-Repositories" bezeichnet. Es geht jedoch 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. In öffentlichen Repositories können Inhalte für alle freigegeben werden. In Unternehmensrepositories werden interne Abhängigkeiten intern verwaltet.
Sie können Ihr Projekt auch in Unterprojekte (in Android Studio auch als „Module“ bezeichnet) modularisieren, die auch als Abhängigkeiten verwendet werden können. Jedes untergeordnete Projekt generiert Ausgaben (z. B. JAR-Dateien), die von untergeordneten Projekten oder Ihrem übergeordneten Projekt verwendet werden können. Die Build-Dauer kann dadurch verkürzt werden, weil isoliert wird, welche Teile neu erstellt werden müssen. 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-App erstellen, sollten Sie in der Regel mehrere Varianten erstellen. Varianten enthalten anderen Code oder werden mit anderen Optionen erstellt. Sie bestehen aus Buildtypen und Produktvarianten.
Build-Typen variieren die deklarierten Build-Optionen. Standardmäßig richtet AGP die Build-Typen „Release“ und „Debuggen“ ein. Sie können diese jedoch anpassen und weitere hinzufügen (z. B. für Staging- oder interne Tests).
Ein Debug-Build verkleinert oder verschleiert Ihre Anwendung nicht, beschleunigt den Build und behält alle Symbole unverändert bei. Außerdem wird die Anwendung als „Debug-fähig“ markiert, sie wird mit einem generischen Fehlerbehebungsschlüssel signiert und ermöglicht den Zugriff auf die installierten Anwendungsdateien auf dem Gerät. 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 Release-Schlüssel signiert und die installierten Anwendungsdateien werden geschützt.
Mithilfe von Produktvarianten können Sie die enthaltenen Quell- und Abhängigkeitsvarianten für die Anwendung ändern. Sie können beispielsweise „Demo“- und „vollständige“ Varianten für Ihre Anwendung oder vielleicht „kostenlose“ und „kostenpflichtige“ Varianten erstellen. Sie schreiben Ihre gemeinsame Quelle in ein "main"-source set-Verzeichnis und überschreiben oder fügen die Quelle in einen nach dem Flavor benannten Quellsatz hinzu oder fügen ihn hinzu.
AGP erstellt Varianten für jede Kombination aus Build-Typ und Produktgeschmack. Wenn Sie keine Flavor-Varianten definieren, werden die Varianten nach den Build-Typen benannt. Wenn Sie beides definieren, heißt die Variante <flavor><Buildtype>
. Mit den Buildtypen release
und debug
sowie den Varianten demo
und full
erstellt AGP beispielsweise die folgenden Varianten:
demoRelease
demoDebug
fullRelease
fullDebug
Nächste Schritte
Nachdem Sie sich die Build-Konzepte angesehen haben, sehen Sie sich die Android-Buildstruktur in Ihrem Projekt an.