Mit Sammlungen den Überblick behalten
Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.
Es gibt keine einzelne Modularisierungsstrategie, die für alle Projekte geeignet ist. Aufgrund von
Da Gradle flexibel ist, gibt es nur wenige Einschränkungen
um ein Projekt zu organisieren. Diese Seite bietet einen Überblick über einige allgemeine Regeln und häufige
Mustern, die du bei der Entwicklung
mehrteiliger Android-Apps nutzen kannst.
Prinzip der hohen Kohäsion und niedriger Kopplung
Eine Möglichkeit zur Charakterisierung einer modularen Codebasis wäre die Verwendung der Kopplung
und cohesion-Eigenschaften. Die Kopplung misst den Grad, in dem Module
voneinander abhängig sind. Kohäsion misst in diesem Kontext, wie die Elemente einer
einzelnen Modulen zusammenhängen. In der Regel sollten Sie
geringe Kopplung und hohe Kohäsion:
Geringe Kopplung bedeutet, dass die Module so unabhängig wie möglich von den
sodass Änderungen an einem Modul keine oder nur minimale Auswirkungen
anderen Modulen. Module sollten keine Kenntnisse über das Innenleben eines
anderen Modulen.
Hohe Kohäsion bedeutet, dass Module eine Sammlung von Code enthalten sollten,
agiert als System. Sie sollten klar definierte Verantwortlichkeiten haben und
innerhalb der Grenzen eines bestimmten Domänenwissens liegen. Beispiel-E-Book ansehen
. Es könnte unangemessen sein, Code für Buch und Zahlung zu mischen
im selben Modul,
da es sich um unterschiedliche Funktionsbereiche handelt.
Modultypen
Die Art und Weise, wie Sie Ihre Module organisieren, hängt hauptsächlich von Ihrer Anwendungsarchitektur ab. Darunter
sind einige gängige Modultypen, die Sie in Ihrer App einführen können,
unserer empfohlenen Anwendungsarchitektur.
Datenmodule
Ein Datenmodul enthält normalerweise ein Repository, Datenquellen und Modellklassen. Die
drei Hauptaufgaben eines Datenmoduls sind:
Alle Daten und Geschäftslogik einer bestimmten Domain verkapseln: Alle Daten
-Moduls verantwortlich für die Verarbeitung von Daten sein,
. Es kann viele Datentypen verarbeiten, solange sie zusammenhängen.
Repository als externe API verfügbar machen: Die öffentliche API von Daten.
sollte ein Repository sein, da sie dafür verantwortlich sind,
für den Rest der App.
So blenden Sie alle Implementierungsdetails und Datenquellen aus:
Datenquellen sollten nur für Repositories aus demselben Modul zugänglich sein.
Sie bleiben nach außen verborgen. Sie können dies erzwingen, indem Sie die
private oder internal Sichtbarkeits-Keyword.
Abbildung 1. Beispiele für Datenmodule und deren Inhalte.
Funktionsmodule
Eine Funktion ist ein isolierter Teil der Funktionalität einer App, der in der Regel
auf einem Bildschirm oder einer Reihe von eng verwandten Bildschirmen, z. B. Anmeldungen oder Bezahlvorgänge,
Ablauf. Verfügt Ihre App über eine Leiste am unteren Rand,
ist eine Funktion.
Abbildung 2: Jeder Tab dieser Anwendung kann als Funktion definiert werden.
Die Funktionen sind mit Bildschirmen oder Zielen in Ihrer App verknüpft. Dementsprechend wird
haben sie wahrscheinlich eine zugehörige Benutzeroberfläche und ViewModel, um ihre Logik zu verarbeiten.
und Bundesstaat. Ein einzelnes Feature muss nicht auf eine einzelne Ansicht oder
Navigationsziel. Funktionsmodule hängen von Datenmodulen ab.
Abbildung 3: Beispiele für Funktionsmodule und ihre Inhalte.
App-Module
App-Module sind ein Einstiegspunkt für die Anwendung. Sie hängen von der Funktion ab,
Module und bieten normalerweise Root-Navigation. Ein einzelnes App-Modul kann kompiliert werden
mithilfe von Build-Varianten in verschiedene Binärprogramme umwandeln.
<ph type="x-smartling-placeholder"></ph>
Abbildung 4: *Demo* und *vollständiges* Diagramm zu Abhängigkeitsmodulen für Produkt-Flavor-Module.
Wenn Ihre App auf mehrere Gerätetypen ausgerichtet ist, etwa „Auto“, „Wear“ oder „TV“, definieren Sie für jeden Gerätetyp ein App-Modul. So lassen sich plattformspezifische
Abhängigkeiten.
Abbildung 5: Diagramm mit Abhängigkeit von Wear-App
Gängige Module
Gängige Module, auch als Kernmodule bezeichnet, enthalten Code, den andere Module
die Sie häufig verwenden. Sie reduzieren die Redundanz und stellen keine bestimmte Schicht in
die Architektur einer App. Im Folgenden finden Sie Beispiele für gängige Module:
UI-Modul: Wenn Sie benutzerdefinierte UI-Elemente oder ein ausgeklügeltes Branding in Ihren
App ist, sollten Sie erwägen, Ihre Widget-Sammlung in einem Modul
wiederverwendbar. Dies könnte dazu beitragen, Ihre UI auf allen
unterschiedliche Funktionen. Sind Ihre Themen beispielsweise zentralisiert, können Sie vermeiden,
bei einem Rebranding eine schmerzhafte Refaktorierung ist.
Analytics-Modul: Tracking wird oft durch Geschäftsanforderungen bestimmt.
für die Softwarearchitektur wenig berücksichtigen. Analytics-Tracker sind
die oft in vielen zusammenhängenden
Komponenten verwendet werden. In diesem Fall kann es sein,
empfiehlt es sich, ein eigenes
Analytics-Modul zu haben.
Netzwerkmodul: Wenn viele Module eine Netzwerkverbindung erfordern, können Sie
haben Sie die Möglichkeit, ein Modul speziell für die Bereitstellung eines HTTP-Clients zu verwenden. Es ist
besonders nützlich, wenn Ihr Client
eine benutzerdefinierte Konfiguration benötigt.
Dienstprogrammmodul: Dienstprogramme, auch als Hilfsprogramme bezeichnet, sind in der Regel kleine Teile.
die in der gesamten Anwendung wiederverwendet werden. Beispiele für Dienstprogramme:
eine Funktion zum Formatieren von Währungen, eine E-Mail-Validierung oder
.
Testmodule
Testmodule sind Android-Module, die nur zu Testzwecken verwendet werden.
Die Module enthalten Testcode, Testressourcen und Testabhängigkeiten, die nur
sind zum Ausführen von Tests erforderlich und werden während der Laufzeit der Anwendung nicht benötigt.
Testmodule werden erstellt, um testspezifischen Code vom Hauptcode zu trennen
-Anwendung, wodurch der Modulcode einfacher zu verwalten und zu pflegen ist.
Anwendungsfälle für Testmodule
Die folgenden Beispiele zeigen Situationen, in denen Testmodule
kann besonders vorteilhaft sein:
Gemeinsam genutzter Testcode: Wenn Ihr Projekt mehrere Module und einige
der Testcode auf mehr als ein Modul anwendbar ist, können Sie
um den Code zu teilen. So lassen sich Duplikate vermeiden und der Test
einfacher zu verwalten. Ein gemeinsamer Testcode kann Dienstprogrammklassen oder
Funktionen wie benutzerdefinierte Assertions oder Matcher sowie Testdaten wie
simulierten JSON-Antworten.
Sauberere Build-Konfigurationen: Testmodule ermöglichen eine sauberere
Build-Konfigurationen erstellen, da sie eine eigene build.gradle-Datei haben können. Das solltest du nicht tun
die Datei build.gradle deines App-Moduls mit Konfigurationen,
nur für Tests relevant.
Integrationstests: Testmodule können zum Speichern der Integration verwendet werden.
Tests, mit denen Interaktionen zwischen
verschiedenen Teilen der App getestet werden.
einschließlich Benutzerschnittstelle, Geschäftslogik, Netzwerkanfragen und Datenbankabfragen.
Umfangreiche Anwendungen: Testmodule sind besonders nützlich für
umfangreichen Anwendungen mit komplexen Codebasen und mehreren Modulen. In solchen Fällen
können Testmodule dazu beitragen, die Organisation und Verwaltbarkeit des Codes zu verbessern.
<ph type="x-smartling-placeholder"></ph>
Abbildung 6. Testmodule können verwendet werden, um Module zu isolieren, die andernfalls voneinander abhängig wären.
Kommunikation zwischen Modulen
Module sind selten vollständig getrennt und stützen sich oft auf andere Module und
mit ihnen kommunizieren können. Es ist wichtig, die Kopplung gering zu halten, auch wenn Module
zusammenarbeiten und Informationen austauschen. Manchmal direkt
Kommunikation zwischen zwei Modulen ist entweder nicht wünschenswert, wie im Fall von
Architektureinschränkungen. Möglicherweise ist es unmöglich, wie z. B. bei zyklischen
Abhängigkeiten.
Abbildung 7: Aufgrund zyklischer Abhängigkeiten ist eine direkte wechselseitige Kommunikation zwischen Modulen nicht möglich. Ein Vermittlungsmodul ist erforderlich, um den Datenfluss zwischen zwei anderen unabhängigen Modulen zu koordinieren.
Um dieses Problem zu lösen, kann ein drittes Modul die Vermittlung
zwischen zwei anderen Modulen. Das Vermittlermodul kann Nachrichten von beiden
und leiten Sie sie bei Bedarf weiter. In unserer Beispiel-App
Bildschirm muss wissen, welches Buch gekauft werden soll,
separaten Bildschirm, der Teil einer anderen Funktion ist. In diesem Fall
„Mediator“ ist das Modul mit dem Navigationsdiagramm (in der Regel ein App-Modul).
In diesem Beispiel verwenden wir die Navigation, um die Daten von der Home-Funktion an die
der Direktkauf-Funktion mithilfe der Komponente Navigation.
navController.navigate("checkout/$bookId")
Das Checkout-Ziel erhält eine Buch-ID als Argument, die es verwendet, um
Informationen über das Buch abzurufen. Mit dem Ziehpunkt für den gespeicherten Status können Sie
Navigationsargumente im ViewModel eines Zielelements abrufen.
class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {
val uiState: StateFlow<CheckoutUiState> =
savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
// produce UI state calling bookRepository.getBook(bookId)
}
…
}
Sie sollten Objekte nicht als Navigationsargumente übergeben. Verwenden Sie stattdessen einfache IDs.
mit denen Funktionen auf die gewünschten Ressourcen aus der Datenschicht zugreifen und diese laden können.
So halten Sie die Kopplung gering und verletzen nicht die Single Source of Truth
Prinzip.
Im folgenden Beispiel hängen beide Funktionsmodule von demselben Datenmodul ab. Dieses
ermöglicht, die Datenmenge zu minimieren, die das Vermittlermodul benötigt
um die Kopplung zwischen den Modulen gering zu halten. Anstelle von
sollten Module primitive IDs austauschen und die Ressourcen aus einer
Modul für freigegebene Daten.
Abbildung 8: Zwei Funktionsmodule, die auf einem gemeinsamen Datenmodul basieren.
Abhängigkeitsumkehr
Eine Abhängigkeitsumkehr liegt vor, wenn Sie Ihren Code so organisieren, dass die Abstraktion
getrennt von einer konkreten Implementierung.
Abstraktion: Ein Vertrag, der festlegt, wie Komponenten oder Module
Anwendungen miteinander interagieren. Abstraktionsmodule definieren die API von
Ihres Systems und enthalten Schnittstellen und Modelle.
Konkrete Implementierung: Module, die vom Abstraktionsmodul abhängen
und das Verhalten einer Abstraktion zu implementieren.
Module, die auf dem im Abstraktionsmodul definierten Verhalten basieren, sollten nur
hängen von der Abstraktion selbst ab, nicht von den spezifischen Implementierungen.
Abbildung 9: Anstelle von übergeordneten Modulen, die direkt von Low-Level-Modulen abhängig sind, hängen übergeordnete Module und Implementierungsmodule vom Abstraktionsmodul ab.
Beispiel
Stellen Sie sich ein Funktionsmodul vor, für das eine Datenbank benötigt wird. Das Funktionsmodul ist
wie die Datenbank implementiert wird, sei es eine lokale
Raumdatenbank oder eine Datenbank
Remote-Firestore-Instanz. Er muss nur die Anwendungsdaten speichern und lesen.
Dazu ist das Funktionsmodul auf das Abstraktionsmodul angewiesen,
als bei einer bestimmten Datenbankimplementierung. Diese Abstraktion definiert die
Database API verwenden können. Mit anderen Worten, es legt die Regeln für die Interaktion mit der
Datenbank. Dadurch kann das Funktionsmodul jede Datenbank verwenden,
die zugrunde liegenden Implementierungsdetails kennen.
Das konkrete Implementierungsmodul liefert die eigentliche Implementierung des
Im Abstraktionsmodul definierte APIs Dazu muss die Implementierung
hängt auch vom Abstraktionsmodul ab.
Abhängigkeitsinjektion
Sie fragen sich jetzt vielleicht, wie das Funktionsmodul mit dem
Implementierungsmodul. Die Antwort lautet Dependency Injection. Die Funktion
die erforderliche Datenbankinstanz
nicht direkt erstellt. Stattdessen werden sie
gibt an, welche Abhängigkeiten benötigt werden. Diese Abhängigkeiten werden dann bereitgestellt,
extern, in der Regel im App-Modul.
Die Trennung Ihrer APIs und ihrer Implementierungen bietet folgende Vorteile:
Austauschbarkeit: Mit klarer Trennung von API und Implementierung
können Sie mehrere Implementierungen für dieselbe API entwickeln und
ohne den Code zu ändern,
der die API verwendet. Dabei kann es sich um
besonders nützlich, wenn Sie unterschiedliche
Fähigkeiten oder Verhalten in verschiedenen Kontexten. Beispiel: Ein simulierter
für Tests und eine echte
Implementierung für die Produktion.
Entkopplung: Die Trennung bedeutet, dass Module, die Abstraktionen verwenden,
von einer bestimmten Technologie abhängen. Wenn Sie Ihre Datenbank von
noch Raum für Firestore wäre, wäre es einfacher, da die Änderungen nur
in dem jeweiligen Modul für die Aufgabe (Implementierungsmodul) stattfinden
mit der API Ihrer Datenbank
andere Module beeinflussen.
Testbarkeit: Die Trennung von APIs von ihren Implementierungen kann erheblich sein.
das Testen erleichtern. Sie können Testfälle für die API-Verträge schreiben. Sie können
verschiedene Implementierungen verwenden,
um verschiedene Szenarien und Grenzfälle zu testen,
einschließlich simulierter Implementierungen.
Verbesserte Build-Leistung: Wenn Sie eine API und ihre
die Implementierung in verschiedene Module,
das Build-System nicht zwingen, die Module neu zu kompilieren,
API-Modul. Dies führt zu kürzeren Build-Daueren und erhöhter Produktivität,
insbesondere bei großen Projekten,
bei denen die Build-Dauer sehr lange dauern kann.
Wann trennen
Es ist sinnvoll, Ihre APIs von ihren Implementierungen in der
folgenden Fällen:
Vielfältige Funktionen: Wenn Sie Teile Ihres Systems
ermöglicht eine klare API die
Austauschbarkeit verschiedener
Implementierungen. Angenommen, Sie haben ein Rendering-System, das OpenGL
oder Vulkan oder einem Abrechnungssystem, das mit Google Play oder Ihrer internen Abrechnung funktioniert
der API erstellen.
Mehrere Anwendungen: Wenn Sie mehrere Anwendungen mit
gemeinsame Funktionen für
verschiedene Plattformen nutzen, können Sie
spezifische Implementierungen
für jede Plattform entwickeln.
Unabhängige Teams: Durch die Trennung können verschiedene Entwickler oder Teams
gleichzeitig an verschiedenen Teilen
der Codebasis arbeiten. Entwickler sollten sich auf
zu den API-Verträgen und ihrer korrekten Verwendung. Sie müssen nicht
die Implementierungsdetails anderer Module.
Große Codebasis: Wenn die Codebasis groß oder komplex ist und die API voneinander trennt.
der Implementierung den Code überschaubarer machen. So können Sie die
in detailliertere, verständliche und
wartbare Einheiten unterteilt.
Implementierung
So implementieren Sie die Abhängigkeitsumkehr:
Abstraktionsmodul erstellen: Dieses Modul sollte APIs (Schnittstellen) enthalten.
und Modelle), die das Verhalten Ihrer Funktion definieren.
Implementierungsmodule erstellen: Implementierungsmodule sollten auf dem
API-Modul und implementieren das Verhalten einer Abstraktion.
<ph type="x-smartling-placeholder"></ph>
<ph type="x-smartling-placeholder">
</ph>
Abbildung 10: Implementierungsmodule hängen vom Abstraktionsmodul ab.
Machen Sie übergeordnete Module von Abstraktionsmodulen abhängig: Anstelle von
direkt von einer spezifischen Implementierung abhängig machen,
Abstraktionsmodule. Für übergeordnete Module ist keine Kenntnis der Implementierung erforderlich.
benötigen sie nur den Vertrag (API).
<ph type="x-smartling-placeholder"></ph>
<ph type="x-smartling-placeholder">
</ph>
Abbildung 11: High-Level-Module sind von Abstraktionen und nicht von der Implementierung abhängig.
Implementierungsmodul bereitstellen: Abschließend müssen Sie das
Implementierung für Ihre Abhängigkeiten. Die jeweilige Implementierung hängt davon ab,
In der Regel ist das App-Modul dafür aber gut geeignet.
Um die Implementierung bereitzustellen, geben Sie sie als Abhängigkeit für die von Ihnen ausgewählte
eine Variante oder einen Testquellensatz erstellen.
<ph type="x-smartling-placeholder"></ph>
<ph type="x-smartling-placeholder">
</ph>
Abbildung 12: Das App-Modul bietet die tatsächliche Implementierung.
Allgemeine Best Practices
Wie anfangs erwähnt wird, gibt es nicht nur einen einzigen richtigen Weg,
App mit mehreren Modulen. So wie es viele Softwarearchitekturen gibt,
eine App zu modularisieren. Dennoch gilt die folgende allgemeine
Empfehlungen können Ihnen helfen, Ihren Code lesbarer, besser wartbar
überprüfbar.
Konfiguration konsistent halten
Jedes Modul führt zu einem zusätzlichen Konfigurationsaufwand. Wenn die Anzahl Ihrer Module
einen bestimmten Schwellenwert erreicht, wird die Verwaltung einer konsistenten Konfiguration
Herausforderung. Zum Beispiel ist es wichtig, dass Module Abhängigkeiten
Version. Wenn Sie eine große Anzahl von Modulen aktualisieren müssen,
Abhängigkeitsversion ist nicht nur ein Aufwand, sondern auch ein Raum für potenzielle
Fehler. Um dieses Problem zu lösen,
können Sie eines der Gradle-Tools verwenden,
Konfiguration zentralisieren:
Versionskataloge sind eine typsichere Liste von Abhängigkeiten.
die bei der Synchronisierung von Gradle generiert werden. Hier können Sie alle Ihre
Abhängigkeiten und ist für alle Module
eines Projekts verfügbar.
Verwenden Sie Konventions-Plug-ins, um die Build-Logik zwischen Modulen freizugeben.
Möglichst wenig preisgeben
Die öffentliche Oberfläche eines Moduls sollte minimal sein und nur die
die wichtigsten Infos. Es sollten keine Implementierungsdetails nach außen gelangen. Umfang
alles so weit wie möglich zu verwirklichen. private oder internal von Kotlin verwenden
Sichtbarkeitsbereich, um das Modul für die Deklarationen privat zu machen. Bei der Deklaration
Abhängigkeiten in Ihrem Modul verwenden, bevorzugen Sie implementation gegenüber api. Letzteres
stellt den Nutzern Ihres Moduls transitive Abhängigkeiten bereit. Mit
Implementierung kann die Build-Dauer verkürzen, da die Anzahl der Module reduziert wird.
die neu aufgebaut werden müssen.
Ich bevorzuge Kotlin und Java-Module
Android Studio unterstützt drei grundlegende Modultypen:
App-Module sind ein Einstiegspunkt für Ihre Anwendung. Sie können Folgendes enthalten:
Quellcode, Ressourcen, Assets und AndroidManifest.xml. Die Ausgabe eines
Das App-Modul ist ein Android App Bundle (AAB) oder ein Android-Anwendungspaket
(APK).
Bibliotheksmodule haben denselben Inhalt wie die App-Module. Sie sind
von anderen Android-Modulen als Abhängigkeit verwendet. Ausgabe eines Bibliotheksmoduls
die ein Android-Archiv sind, sind strukturell identisch mit App-Modulen,
werden zu einer Android Archive-Datei (AAR) kompiliert, die später von anderen
Module als Abhängigkeit. Mit einem Bibliotheksmodul
die gleiche Logik und Ressourcen in vielen Anwendungsmodulen kapseln und wiederverwenden.
Die Kotlin- und Java-Bibliotheken enthalten keine Android-Ressourcen, -Assets oder
Manifestdateien.
Da Android-Module viel Aufwand verursachen, empfiehlt es sich, das Modul
Kotlin- oder Java-Code so gut wie möglich verwenden.
Alle Inhalte und Codebeispiele auf dieser Seite unterliegen den Lizenzen wie im Abschnitt Inhaltslizenz beschrieben. Java und OpenJDK sind Marken oder eingetragene Marken von Oracle und/oder seinen Tochtergesellschaften.
Zuletzt aktualisiert: 2024-08-29 (UTC).
[[["Leicht verständlich","easyToUnderstand","thumb-up"],["Mein Problem wurde gelöst","solvedMyProblem","thumb-up"],["Sonstiges","otherUp","thumb-up"]],[["Benötigte Informationen nicht gefunden","missingTheInformationINeed","thumb-down"],["Zu umständlich/zu viele Schritte","tooComplicatedTooManySteps","thumb-down"],["Nicht mehr aktuell","outOfDate","thumb-down"],["Problem mit der Übersetzung","translationIssue","thumb-down"],["Problem mit Beispielen/Code","samplesCodeIssue","thumb-down"],["Sonstiges","otherDown","thumb-down"]],["Zuletzt aktualisiert: 2024-08-29 (UTC)."],[],[]]