Berücksichtigung der Modularisierung von Android-Projekten
Veröffentlicht: 2019-08-21Wenn ein Projekt einen bestimmten Umfang erreicht, wird die weitere Arbeit damit in einem einzelnen Modul weniger effektiv. Die Modularisierung wird dann zu einer effektiven Lösung.
4 Vorteile der Modularisierung
Bevor man sich für eine Modularisierung entscheidet, ist es gut, sich darüber im Klaren zu sein, worum es geht. Zu den Vorteilen einer modularen Android-Projektstruktur gehören:
Bessere Code-Isolation
Jedes Modul kann seine öffentlichen API-Schnittstellen offenlegen und Implementierungsdetails verbergen. Bei einem einzelnen Modul können Sie nicht ganz sicher sein, dass seine Implementierung gut versteckt ist (insbesondere in Kotlin, wo kein Paketsichtbarkeitsmodifikator verfügbar ist).
Einfachere Evaluierung neuer Technologien
Wenn Sie ein Modul erstellen, können Sie das neue Architekturmuster oder die neue Bibliothek überprüfen, ohne die anderen Module zu beeinflussen.
Reduzierte Projekterstellungszeit
Das Ändern eines Moduls erfordert den Neuaufbau dieses Moduls und anderer, die davon abhängen. Sehen Sie genau, wie es funktioniert, indem Sie diese Dokumentation lesen: Android-Entwickler. Abhängigkeitskonfigurationen
Bequemeres Arbeiten
Es wird einfacher, kleinere und isolierte Codeteile zu analysieren/debuggen/umzugestalten. Außerdem wird das Onboarding neuer Entwickler schneller vonstatten gehen.
Diese Vorteile klingen genug, um Sie davon zu überzeugen, mit dem Modularisierungsprozess zu beginnen, aber wie fangen Sie an?
#1: Identifizieren Sie Ihre Module und ihre Beziehungen
Es gibt zwei Ansätze, um Ihre Module zu erkennen: nach Feature und nach Layer.
Unter Funktionsmodulen können Sie einige Bereiche der App verstehen, die Benutzern zur Verfügung stehen (z. B. Anmeldung, Dashboard, Profil usw.). Diese Bereiche können aus einem einzelnen Bildschirm oder einer Reihe von Bildschirmen bestehen, die einen bestimmten Prozess abdecken. Module dieses Typs können nicht von Modulen desselben Typs abhängen.
Nachdem Sie die Funktionen identifiziert haben, müssen Sie auf jeden Fall allgemeine Funktionalitäten extrahieren, die von einigen oder sogar allen Modulen benötigt werden. Diese Module können für separate Architekturschichten (wie persistente Speicherung, Netzwerke, Navigation, UI-Komponenten …) oder die Geschäftslogik der Verarbeitung einiger Daten verantwortlich sein, die von einer Reihe von Funktionen verwendet werden. Diese Art von Modulen werden normalerweise als Bibliotheken bezeichnet. Bibliotheksmodule können Abhängigkeitsbäume erstellen.
Neben Funktions- und Bibliotheksmodulen wird auch ein Modul benötigt, um horizontale Verbindungen zwischen anderen Modulen zu verwalten (mehr dazu im nächsten Punkt). Dieses Modul enthält eine benutzerdefinierte Anwendungsklasse und eine Einrichtung der Abhängigkeitsinjektion. Kein anderes Modul kann von diesem Modul abhängen, aber dieses Modul hängt von allen anderen im Projekt ab.

Unter Berücksichtigung der obigen Definitionen kann die Modulhierarchie wie folgt aussehen:
#2: Dependency-Injection-Setup
Trotz Abhängigkeiten zwischen Projektmodulen sollten Sie auch Dolchabhängigkeiten einrichten. Dagger bietet zwei Möglichkeiten, Abhängigkeiten zu deklarieren: Unterkomponenten und Komponentenabhängigkeit .
Die Unterkomponentenabhängigkeit von Dagger erfordert, dass Eltern alle abhängigen untergeordneten Elemente deklarieren. Zwischen Projektmodulen würde diese Art von Beziehung nicht funktionieren, da sie die Richtung der Projektmodulabhängigkeit umkehrt. Es kann jedoch innerhalb separater Projektmodule verwendet werden.

Die Abhängigkeit von Dagger-Komponenten ist flexibler, da ein untergeordnetes Element deklarieren kann, dass es vom übergeordneten Element abhängig ist. Dadurch ist es möglich, diese Art der Abhängigkeit zwischen einzelnen Projektmodulen zu nutzen.
Irgendwann stellen Sie vielleicht fest, dass ein Modul begrenztes Wissen über ein anderes Modul erfordert. Ein sehr gutes Beispiel hierfür kann die Navigation zwischen Feature-Modulen sein. Das Bereitstellen dieser Art von Beziehung wird oft als horizontale Abhängigkeit bezeichnet. Um diesen Kommunikationskanal zwischen separaten Modulen zu erstellen, werden zusätzliche Module mit Schnittstellen benötigt, die diese Kommunikation beschreiben, und ein Modul, das eine Implementierung an die deklarierten Schnittstellen bindet.
Die Einrichtung der Projektmodulabhängigkeit zur Verwaltung der horizontalen Abhängigkeit ist in der folgenden Abbildung dargestellt:

Beispielcode für solche Beziehungen finden Sie in einem Projekt am Ende des Artikels.
#3: Gradle-Setup
Jedes Projektmodul hat seine gradle.build, die ziemlich gleich ist, außer dass zusätzliche Abhängigkeiten und Plugins angewendet werden. Daher ist es schön, sich wiederholende Konfigurationen in eine Gradle-Datei im Stammverzeichnis des Projektverzeichnisses zu extrahieren. Eine solche Datei kann auch allgemeine Gradle-Aufgaben registrieren, um eine statische Codeanalyse oder Komponententests auszuführen.
Codeschnipsel eines solchen allgemeinen Setups finden Sie hier:
afterEvaluate { project -> def isAndroid = project.plugins.hasPlugin('com.android.library') || project.plugins.hasPlugin('com.android.application') setupModule(isAndroid) setupCommonTestDependencies(isAndroid) setupCommonTasks(isAndroid) } def setupModule(isAndroid) { if (isAndroid) { android { compileSdkVersion projectCompileSdk defaultConfig { minSdkVersion projectMinSdk targetSdkVersion projectTargetSdk } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError true checkReleaseBuilds false checkAllWarnings true warningsAsErrors true def lintBaseline = file("quality/lint-baseline.xml") if (lintBaseline.exists()) baseline lintBaseline } } } else { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } } def setupCommonTestDependencies(isAndroid) { dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.assertj:assertj-core:${assertJVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:${mockitoKotlinVersion}" if (isAndroid) { androidTestImplementation "androidx.test.ext:junit:${axTestJUnitVersion}" androidTestImplementation "androidx.test.espresso:espresso-core:${axEspressoLibVersion}" } } } def setupCommonTasks(isAndroid) { if (isAndroid) { tasks.register("unitTest") { task -> task.dependsOn(testDebugUnitTest) } } else { tasks.register("unitTest") { task -> task.dependsOn(test) } } }
Fazit
Dieser Artikel ist weder erschöpfend noch eine vollständige Anleitung zum Modularisieren eines Android-Projekts. Aber ich denke, es greift Aspekte auf, die Sie berücksichtigen sollten, wenn Sie mit der Projektmodularisierung beginnen.
Ein Stück Code zur Darstellung einer horizontalen Abhängigkeit finden Sie unter dem Link.
Möchten Sie eine native Anwendung für Android erstellen? Wählen Sie Miquido!