Android-Anwendungen werden in der Regel mit dem Build-System Gradle 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 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.
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 die Aufgaben registriert und über ihre Eingaben 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 auch für Kotlin und andere Sprachen, aber andere Plug-ins dienen dazu, Plug-ins zu erweitern. Das protobuf
-Plug-in soll beispielsweise bestehenden Plug-ins wie AGP oder java-library
protobuf-Unterstützung hinzufügen.
Gradle bevorzugt Konventionen gegenüber Konfigurationen. Plug-ins haben daher standardmäßig gute Standardwerte. Sie können den Build jedoch über eine deklarative domainspezifische Sprache (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 steuert das „Wie“. Diese Konfiguration wird in mehreren Builddateien in Ihrem Projekt (und in den untergeordneten Projekten) angegeben.
Aufgabeneingaben können Dateien und Verzeichnisse sowie andere Informationen sein, die als Java-Typen codiert sind (Ganzzahl, Strings oder benutzerdefinierte Klassen). Ausgaben können nur Verzeichnisse oder Dateien sein, da sie auf das Laufwerk 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 Buildlogik und die Aufgabendeklarationen auf Plugins beschränken, die Sie oder eine andere Person definieren, und angeben, wie Sie diese Logik in Ihren Builddateien 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 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 wird die Anwendung tatsächlich erstellt. 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 wurden). Dieser Graph stellt die Beziehung zwischen Aufgaben dar, die entweder in der Deklaration einer Aufgabe explizit angegeben ist oder auf ihren Eingaben und Ausgaben basiert. Wenn die Eingabe einer Aufgabe die Ausgabe einer anderen Aufgabe ist, muss sie nach der anderen Aufgabe ausgeführt werden. In dieser Phase werden veraltete Aufgaben in der im Graphen 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 Gradle-Build-Lebenszyklus.
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 die DSL erweitern, um die für ihre Aufgaben erforderlichen Daten zu konfigurieren.
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 wirkt der Code in Ihren Build-Dateien eher wie eine Datenspezifikation.
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 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. Normalerweise wird dies 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, 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. So lässt sich die Buildzeit verbessern, indem die Teile isoliert werden, die neu erstellt werden müssen, und die Zuständigkeiten in der Anwendung besser voneinander getrennt werden.
Weitere Informationen zum Angeben von Abhängigkeiten finden Sie unter Build-Abhängigkeiten hinzufügen.
Build-Varianten
Wenn Sie eine Android-Anwendung 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 Buildtypen „Release“ und „Debug“ ein. Sie können sie 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, wodurch der Build beschleunigt und alle Symbole unverändert beibehalten werden. Außerdem wird die Anwendung als „debuggable“ (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 Release-Schlüssel signiert und die installierten Anwendungsdateien werden geschützt.
Mit Produktvarianten können Sie die enthaltene Quelle und die Abhängigkeitsvarianten für die Anwendung ändern. Sie können beispielsweise eine „Demo“- und eine „Vollversion“ Ihrer Anwendung oder eine „kostenlose“ und eine „kostenpflichtige“ Version erstellen. Sie schreiben die gemeinsame Quelle in einem Hauptverzeichnis des Quellsatzes und überschreiben oder fügen die Quelle in einem Quellsatz hinzu, der nach dem Flavor benannt ist.
AGP erstellt Varianten für jede Kombination aus Build-Typ und Produktvariante. Wenn Sie keine Flavors definieren, werden die Varianten nach den Buildtypen benannt. Wenn Sie beides definieren, wird die Variante <flavor><Buildtype>
genannt. 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.