Android-Anwendungen werden in der Regel mit dem Build-System Gradle erstellt. Bevor wir uns mit den Details der Build-Konfiguration 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 zum Analysieren, Kompilieren, Verknüpfen und Packen Ihrer Anwendung oder Bibliothek. Gradle verwendet einen auf Aufgaben basierenden 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 die 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 Plug-in java-library 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. Mit dem Plug-in protobuf soll beispielsweise die Protobuf-Unterstützung zu vorhandenen Plug-ins wie AGP oder java-library hinzugefügt werden.
Gradle bevorzugt Konventionen gegenüber Konfigurationen. Daher werden Plug-ins mit guten Standardwerten ausgeliefert. Sie können den Build jedoch über eine deklarative domänenspezifische Sprache (Domain-Specific Language, 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 Unterprojekten) angegeben.
Aufgabeneingaben können Dateien und Verzeichnisse sowie andere Informationen sein, die als Java-Typen (Ganzzahlen, 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 eine Aufgabenausgabe mit einer anderen Aufgabeneingabe verknüpfen, 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 die Deklaration von Aufgaben in Ihren Build-Dateien, dies kann es jedoch erschweren, Ihren Build zu verstehen und zu verwalten. Sie können beispielsweise Tests für Code in Plug-ins, aber nicht in Build-Dateien schreiben. Stattdessen sollten Sie die Build-Logik und die Aufgabendeklarationen 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 Codeabschnitte ausgeführt, die Sie in Ihren Build-Dateien definieren.
- In der Initialisierungsphase wird festgelegt, welche Projekte und Unterprojekte in den Build einbezogen werden, und es werden Klassenpfade eingerichtet, die Ihre Build-Dateien und angewendeten Plug-ins enthalten. Diese Phase konzentriert sich auf eine Einstellungsdatei, in der Sie die zu erstellenden Projekte und die Speicherorte angeben, von denen Plug-ins und Bibliotheken abgerufen werden sollen.
- In der Konfigurationsphase 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 wurden.
- In der Ausführungsphase 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). Dieser Graph 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 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 unter Gradle Build-Lebenszyklus.
Konfigurations-DSLs
Gradle verwendet eine domänenspezifische Sprache (Domain-Specific Language, DSL) zum Konfigurieren von Builds. Dieser deklarative Ansatz konzentriert sich auf die Angabe Ihrer Daten und nicht auf das Schreiben von Schritt-für-Schritt-Anweisungen (imperativ). Sie können Ihre Build-Dateien mit Kotlin oder Groovy schreiben. Wir empfehlen jedoch dringend, Kotlin zu verwenden.
DSLs sollen es allen, sowohl Domänenexperten 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 ähnelt der DSL-Code diesem:
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 sieht der Code in Ihren Build-Dateien eher wie eine Datenspezifikation aus.
Externe Abhängigkeiten
Das Maven-Build-System führte ein System zur Spezifikation, Speicherung und Verwaltung von Abhängigkeiten ein. Bibliotheken werden in Repositories (Servern oder Verzeichnissen) mit Metadaten gespeichert, einschließlich ihrer Version und Abhängigkeiten von anderen Bibliotheken. Sie geben an, welche Repositories durchsucht werden sollen, welche Versionen der Abhängigkeiten Sie verwenden möchten, und das Build-System lädt sie während des Builds herunter.
Maven-Artefakte werden durch den Gruppennamen (Unternehmen, Entwickler usw.), den Artefaktnamen (den Namen der Bibliothek) und die Version dieses 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, aber es geht 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 gemeinsame Nutzung für alle, und Unternehmens-Repositories halten interne Abhängigkeiten im Unternehmen.
Sie können Ihr Projekt auch in Unterprojekte unterteilen (in Android Studio auch als „Module“ bezeichnet), die auch als Abhängigkeiten verwendet werden können. Jedes Unterprojekt erzeugt Ausgaben (z. B. JAR-Dateien), die von Unterprojekten oder Ihrem Projekt der obersten Ebene verwendet werden können. Dadurch kann die Build-Zeit verkürzt werden, da isoliert wird, welche Teile neu erstellt werden müssen, und 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 Variantenerstellen. 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 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. 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 Release-Schlüssel signiert und die installierten Anwendungsdateien werden geschützt.
Mit Produktvarianten können Sie die enthaltenen Quell- und Abhängigkeits varianten für die Anwendung ändern. Sie können beispielsweise die Varianten „Demo“ und „Vollversion“ oder „Kostenlos“ und „Kostenpflichtig“ für Ihre Anwendung erstellen. Sie schreiben Ihren gemeinsamen Quellcode in ein "main" Source-Set-Verzeichnis und überschreiben oder fügen Quellcode in einem Source-Set hinzu, der nach dem Flavor benannt ist.
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
beides definieren, wird die Variante <flavor><Buildtype> genannt. Bei den Build-Typen release und debug und den Varianten demo und full erstellt AGP beispielsweise folgende Varianten:
demoReleasedemoDebugfullReleasefullDebug
Nächste Schritte
Nachdem Sie nun die Build-Konzepte kennengelernt haben, sehen Sie sich die Android-Build Struktur in Ihrem Projekt an.