Empfehlungen für die Android-Architektur

Auf dieser Seite finden Sie mehrere Architektur-Best Practices und ‑Empfehlungen. Wenn Sie sie übernehmen, können Sie die Qualität, Robustheit und Skalierbarkeit Ihrer App verbessern. Außerdem erleichtern sie die Wartung und das Testen Ihrer App.

Die folgenden Best Practices sind nach Thema gruppiert. Jede Empfehlung hat eine Priorität, die angibt, wie wichtig sie ist. Die Prioritäten sind:

  • Dringend empfohlen:Implementieren Sie diese Vorgehensweise, sofern sie nicht grundlegend mit Ihrem Ansatz kollidiert.
  • Empfohlen:Diese Vorgehensweise wird wahrscheinlich die Qualität Ihrer App verbessern.
  • Optional:Unter bestimmten Umständen kann diese Vorgehensweise die Leistung Ihrer App verbessern.

Geschichtete Architektur

Unsere empfohlene geschichtete Architektur begünstigt die Trennung von Belangen. Die Benutzeroberfläche wird aus Datenmodellen abgeleitet, entspricht dem Prinzip der Single Source of Truth und folgt den Prinzipien des unidirektionalen Datenflusses. Hier sind einige Best Practices für die mehrschichtige Architektur:

Empfehlung Beschreibung
Verwenden Sie eine klar definierte Datenschicht. Die Datenebene stellt Anwendungsdaten für den Rest der App bereit und enthält den Großteil der Geschäftslogik Ihrer App.
  • Erstellen Sie Repositories, auch wenn sie nur eine einzige Datenquelle enthalten.
  • In kleinen Apps können Sie Datenebenentypen in einem data-Paket oder -Modul platzieren.
Verwenden Sie eine klar definierte UI-Ebene. In der UI-Ebene werden die Anwendungsdaten auf dem Bildschirm angezeigt. Sie ist der primäre Punkt für die Nutzerinteraktion. Jetpack Compose ist das empfohlene moderne Toolkit für die Entwicklung der Benutzeroberfläche Ihrer App.
  • In kleinen Apps können Sie Datenebenentypen in einem ui-Paket oder -Modul platzieren.
Weitere Informationen zu Best Practices für den UI-Layer finden Sie unter UI-Layer.
Anwendungsdaten aus der Datenschicht über ein Repository verfügbar machen.

Achten Sie darauf, dass Komponenten in der UI-Schicht wie Composables oder ViewModels nicht direkt mit einer Datenquelle interagieren. Beispiele für Datenquellen:

  • Datenbanken, DataStore, SharedPreferences, Firebase APIs.
  • GPS-Standortanbieter
  • Bluetooth-Datenanbieter
  • Anbieter von Informationen zum Netzwerkverbindungsstatus.
Coroutinen und Flows verwenden Verwenden Sie Coroutinen und Flows, um zwischen Ebenen zu kommunizieren.

Weitere Informationen zu Best Practices für Coroutinen finden Sie unter Best Practices für Coroutinen in Android.

Verwenden Sie eine Domain-Ebene. Verwenden Sie eine Domänenschicht mit Anwendungsfällen, wenn Sie Geschäftslogik, die mit der Datenschicht interagiert, in mehreren ViewModels wiederverwenden oder die Komplexität der Geschäftslogik eines bestimmten ViewModels vereinfachen möchten.

UI-Ebene

Die UI-Ebene dient dazu, die Anwendungsdaten auf dem Bildschirm darzustellen und als primärer Punkt für die Nutzerinteraktion zu fungieren. Hier sind einige Best Practices für die UI-Ebene:

Empfehlung Beschreibung
Folgen Sie dem unidirektionalen Datenfluss (UDF). Halten Sie sich an die Prinzipien des unidirektionalen Datenflusses (UDF), bei denen ViewModels den UI-Status über das Observer-Muster bereitstellen und Aktionen über Methodenaufrufe von der UI empfangen.
Verwenden Sie AAC ViewModels, wenn die Vorteile für Ihre App gelten. Verwenden Sie AAC-ViewModels, um Geschäftslogik zu verarbeiten und Anwendungsdaten abzurufen, um den UI-Status in der Benutzeroberfläche verfügbar zu machen.

Weitere Informationen zu ViewModel-Best Practices finden Sie unter Architekturempfehlungen.

Weitere Informationen zu den Vorteilen von ViewModels finden Sie unter Das ViewModel als Status-Holder für die Geschäftslogik.

Lebenszyklusbewusste Erfassung des UI-Zustands verwenden Erfassen Sie den UI-Status über die UI mit dem entsprechenden lebenszyklusbezogenen Coroutine-Builder collectAsStateWithLifecycle.

Weitere Informationen zu collectAsStateWithLifecycle

Senden Sie keine Ereignisse vom ViewModel an die Benutzeroberfläche. Verarbeiten Sie das Ereignis sofort im ViewModel und lösen Sie mit dem Ergebnis der Verarbeitung des Ereignisses eine Statusaktualisierung aus. Weitere Informationen zu UI-Ereignissen finden Sie unter ViewModel-Ereignisse verarbeiten.
Verwenden Sie eine Anwendung mit nur einer Aktivität. Verwenden Sie Navigation 3, um zwischen Bildschirmen zu wechseln und einen Deeplink zu Ihrer App zu erstellen, wenn Ihre App mehrere Bildschirme hat.
Verwenden Sie Jetpack Compose. Mit Jetpack Compose können Sie neue Apps für Smartphones, Tablets, Faltgeräte und Wear OS entwickeln.

Das folgende Snippet zeigt, wie der UI-Zustand auf lebenszyklusbewusste Weise erfasst wird:

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ViewModel

ViewModels sind dafür verantwortlich, den UI-Status bereitzustellen und auf die Datenschicht zuzugreifen. Hier sind einige Best Practices für ViewModels:

Empfehlung Beschreibung
ViewModels unabhängig vom Android-Lebenszyklus halten ViewModel-Klassen dürfen keine Referenz auf einen beliebigen typbezogenen Typ enthalten. Übergeben Sie Activity, Context oder Resources nicht als Abhängigkeit. Wenn etwas ein Context im ViewModel erfordert, prüfen Sie sorgfältig, ob es sich um die richtige Ebene handelt.
Coroutinen und Flows verwenden

Das ViewModel interagiert mit den Daten- oder Domänenebenen über Folgendes:

  • Kotlin-Flows zum Empfangen von Anwendungsdaten
  • suspend-Funktionen zum Ausführen von Aktionen mit viewModelScope
ViewModels auf Bildschirmebene verwenden

Verwenden Sie keine ViewModels in wiederverwendbaren UI-Elementen. ViewModels sollten in folgenden Fällen verwendet werden:

  • Composable-Funktionen auf Bildschirmebene
  • Ziele oder Diagramme bei Verwendung von Jetpack Navigation

Bei komplexeren Composables oder solchen mit dynamischem Verhalten basierend auf dem Status verwenden Sie rememberViewModelStoreOwner(), um ein ViewModel direkt auf die Aufrufstelle des Composables zu beschränken.

Verwenden Sie einfache Status-Holder-Klassen in wiederverwendbaren UI-Komponenten. Verwenden Sie einfache Klassen für die Statusverwaltung, um die Komplexität in wiederverwendbaren UI-Komponenten zu bewältigen. In diesem Fall kann der Status nach oben verschoben und extern gesteuert werden.
Verwenden Sie nicht AndroidViewModel. Verwenden Sie die Klasse ViewModel und nicht AndroidViewModel. Verwenden Sie die Application-Klasse nicht im ViewModel. Verschieben Sie die Abhängigkeit stattdessen in die UI- oder Datenschicht.
UI-Zustand bereitstellen ViewModel-Daten werden über eine einzelne Property namens uiState für die Benutzeroberfläche bereitgestellt. Wenn auf der Benutzeroberfläche mehrere unabhängige Daten angezeigt werden, kann die VM mehrere Eigenschaften für den UI-Status bereitstellen.
  • Mache uiState zu einem StateFlow.
  • Erstellen Sie uiState mit dem Operator stateIn und der Richtlinie WhileSubscribed(5000), wenn die Daten als Datenstream aus anderen Ebenen der Hierarchie stammen. Codebeispiel
  • In einfacheren Fällen ohne Datenstreams aus der Datenschicht kann ein MutableStateFlow verwendet werden, das als unveränderliches StateFlow verfügbar gemacht wird.
  • Sie können ${Screen}UiState als Datenklasse auswählen, die Daten, Fehler und Ladesignale enthalten kann. Diese Klasse kann auch eine sealed class sein, wenn sich die verschiedenen Status gegenseitig ausschließen.

Das folgende Snippet zeigt, wie Sie den UI-Status über ein ViewModel verfügbar machen:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Lebenszyklus

Beachten Sie die Best Practices für die Arbeit mit dem Aktivitätslebenszyklus:

Empfehlung Beschreibung
Verwenden Sie in Composables lebenszyklusbezogene Effekte, anstatt Activity-Lebenszyklus-Callbacks zu überschreiben.

Überschreiben Sie keine Activity-Lifecycle-Methoden wie onResume, um UI-bezogene Aufgaben auszuführen. Verwenden Sie stattdessen LifecycleEffects von Compose oder lebenszyklusbezogene Coroutine-Scopes:

  • Verwenden Sie LifecycleStartEffect, um synchrone Vorgänge auszuführen, wenn Ihre Aktivität gestartet und beendet wird.
  • Verwenden Sie LifecycleResumeEffect, um synchrone Vorgänge auszuführen, wenn Ihre Aktivität fortgesetzt und pausiert wird.
  • Verwenden Sie repeatOnLifecycle, um asynchrone Aufgaben als Reaktion auf Lebenszyklusereignisse auszuführen.
  • Asynchrone Daten aus Flows mit collectAsStateWithLifecycle erfassen.

Das folgende Snippet zeigt, wie Sie Vorgänge in einem bestimmten Lebenszyklusstatus ausführen:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

Abhängigkeiten verarbeiten

Beachten Sie beim Verwalten von Abhängigkeiten zwischen Komponenten die folgenden Best Practices:

Empfehlung Beschreibung
Verwenden Sie Dependency Injection. Verwenden Sie Best Practices für die Abhängigkeitsinjektion, insbesondere Konstruktorinjektion, wenn möglich.
Bei Bedarf auf eine Komponente beschränken. Beschränken Sie den Bereich auf einen Abhängigkeitscontainer, wenn der Typ veränderliche Daten enthält, die freigegeben werden müssen, oder wenn der Typ teuer zu initialisieren ist und in der App häufig verwendet wird.
Verwenden Sie Hilt. Verwenden Sie Hilt oder manuelle Dependency Injection in einfachen Apps. Verwenden Sie Hilt, wenn Ihr Projekt komplex genug ist, z. B. wenn es Folgendes enthält:
  • Mehrere Bildschirme mit ViewModels
  • WorkManager wird verwendet
  • ViewModels sind auf den Backstack der Navigation beschränkt.

Test

Im Folgenden finden Sie einige Best Practices für das Testen:

Empfehlung Beschreibung
Wissen, was getestet werden soll:

Sofern es sich nicht um ein einfaches „Hello World“-Projekt handelt, sollten Sie es testen. Geben Sie mindestens Folgendes an:

  • Unittests für ViewModels, einschließlich Flows
  • Unit-Tests für Data Layer-Entitäten, d. h. Repositories und Datenquellen
  • UI-Navigationstests, die als Regressionstests in CI nützlich sind
  • Leistungstests mit Macrobenchmark zum Messen des App-Starts und des UI-Renderings
Fakes werden gegenüber Mocks bevorzugt. Weitere Informationen zur Verwendung von Fakes finden Sie unter Test Doubles in Android verwenden.
StateFlows testen Gehen Sie beim Testen von StateFlow so vor:

Weitere Informationen finden Sie unter Was sollte in Android getestet werden? und Compose-Layout testen.

Leistung

Im Folgenden finden Sie einige Best Practices zur Leistungssteigerung:

Empfehlung Beschreibung
Verwenden Sie R8, um Ihre App zu verkleinern und zu optimieren. Aktivieren Sie R8 im vollständigen Modus in Ihrer Build-Konfiguration, um ungenutzten Code zu entfernen, Klassen zu verschleiern und Code neu zu schreiben, um die Laufzeitleistung zu verbessern.
Implementieren Sie Baseline-Profile und Startprofile. Generieren Sie Baseline-Profile und Startprofile, um kritische Codepfade vorzukompilieren. Dadurch werden die Startlatenz (TTID und TTFD) erheblich reduziert und Ruckeln und ANR-Fehler minimiert.
Leistung mit Macrobenchmark testen Verwenden Sie die Macrobenchmark-Bibliothek, um die Leistung Ihrer App zu testen und Regressionen zu verhindern.

Modelle

Beachten Sie die folgenden Best Practices, wenn Sie Modelle in Ihren Apps entwickeln:

Empfehlung Beschreibung
Erstellen Sie ein Modell pro Ebene in komplexen Apps.

In komplexen Apps sollten Sie neue Modelle in verschiedenen Ebenen oder Komponenten erstellen, wenn es sinnvoll ist. Betrachten Sie hierzu folgende Beispiele:

  • Eine Remotedatenquelle kann das Modell, das sie über das Netzwerk empfängt, einer einfacheren Klasse mit nur den Daten zuordnen, die die App benötigt.
  • In Repositories können DAO-Modelle einfacheren Datenklassen mit nur den Informationen zugeordnet werden, die die UI-Schicht benötigt.
  • ViewModel kann Modelle der Datenschicht in UiState-Klassen enthalten.

Namenskonventionen

Beachten Sie beim Benennen Ihrer Codebasis die folgenden Best Practices:

Empfehlung Beschreibung
Methoden zur Namensgebung.
Optional
Verwenden Sie Verbgruppen, um Methoden zu benennen, z. B. makePayment().
Eigenschaften benennen
Optional
Verwenden Sie Wortgruppen, um Eigenschaften zu benennen, z. B. inProgressTopicSelection.
Datenstreams benennen
Optional
Wenn eine Klasse einen Flow-Stream oder einen anderen Stream bereitstellt, lautet die Namenskonvention get{model}Stream, z. B. getAuthorStream(): Flow<Author>. Wenn die Funktion eine Liste von Modellen zurückgibt, verwenden Sie den Plural des Modellnamens: getAuthorsStream(): Flow<List<Author>>.
Implementierungen von Schnittstellen benennen.
Optional
Verwenden Sie aussagekräftige Namen für die Implementierungen von Schnittstellen. Verwenden Sie Default als Präfix, wenn kein besserer Name gefunden werden kann. Für eine NewsRepository-Schnittstelle kann beispielsweise ein OfflineFirstNewsRepository oder InMemoryNewsRepository vorhanden sein. Wenn Sie keinen passenden Namen finden, verwenden Sie DefaultNewsRepository. Stellen Sie fiktiven Implementierungen das Präfix Fake voran, z. B. FakeAuthorsRepository.

Zusätzliche Ressourcen

Weitere Informationen zur Android-Architektur finden Sie in den folgenden zusätzlichen Ressourcen:

Dokumentation

Inhalte ansehen