Zalecenia dotyczące architektury Androida

Na tej stronie znajdziesz kilka sprawdzonych metod i rekomendacji dotyczących architektury. Zastosuj je, aby poprawić jakość, niezawodność i skalowalność aplikacji. Ułatwiają też jej obsługę i testowanie.

Poniższe sprawdzone metody są uporządkowane według tematów. Każdy z nich ma priorytet, który odzwierciedla, jak mocno go poleca zespół. Oto lista priorytetów:

  • Zdecydowanie zalecane: zastosuj tę praktykę, chyba że koliduje ona z Twoim podejściem.
  • Zalecane: ta praktyka może poprawić Twoją aplikację.
  • Opcjonalnie: może to pomóc w ulepszaniu aplikacji w określonych okolicznościach.

Architektura warstwowa

Nasza rekomendowana przez nas architektura warstwowa preferuje rozdzielenie potencjalnych problemów. Wykorzystuje on modele danych dla interfejsu użytkownika, jest zgodny z zasadą dotyczącą jednego źródła danych i działa zgodnie z zasadami jednokierunkowego przepływu danych. Oto kilka sprawdzonych metod tworzenia architektury warstwowej:

Rekomendacja Opis
Użyj wyraźnie zdefiniowanej warstwy danych.
Zdecydowanie zalecane
Warstwa danych ujawnia dane aplikacji pozostałym jej członkom i przechowuje większość logiki biznesowej aplikacji.
  • Twórz repozytoria nawet wtedy, gdy zawierają tylko jedno źródło danych.
  • W małych aplikacjach możesz umieścić typy warstw danych w pakiecie lub module data.
Używaj wyraźnie zdefiniowanej warstwy interfejsu.
Zdecydowanie zalecane
Warstwa interfejsu wyświetla dane aplikacji na ekranie i służy za główny punkt interakcji użytkownika.
  • W małych aplikacjach możesz umieścić typy warstw danych w pakiecie lub module ui.
Więcej sprawdzonych metod dotyczących warstwy UI
Warstwa danych powinna udostępniać dane aplikacji za pomocą repozytorium.
Zdecydowanie zalecane

Komponenty warstwy interfejsu, takie jak obiekty kompozycyjne, aktywności czy obiekty typu ViewModels, nie powinny wchodzić w bezpośrednie interakcje ze źródłem danych. Przykłady źródeł danych:

  • Bazy danych, DataStore, SharedPreferences, Interfejsy API Firebase.
  • Dostawcy lokalizacji GPS.
  • Dostawcy danych Bluetooth.
  • Dostawca stanu połączenia sieciowego.
Użyj korekty i przepływów.
Zdecydowanie zalecane
Do komunikacji między warstwami używaj współpracy i przepływów.

Więcej sprawdzonych metod dotyczących współprogramów

Użyj warstwy domeny.
Polecane w dużych aplikacjach
Użyj warstwy domeny, jeśli chcesz ponownie wykorzystać logikę biznesową, która współdziała z warstwą danych w wielu modelach widoków danych, lub chcesz uprościć złożoność logiki biznesowej konkretnego modelu.

Warstwa interfejsu

Rola warstwy interfejsu to wyświetlanie danych aplikacji na ekranie i stanowienie głównego punktu interakcji użytkownika. Oto kilka sprawdzonych metod dotyczących warstwy UI:

Rekomendacja Opis
Postępuj zgodnie z jednokierunkowym przepływem danych (UDF).
Zdecydowanie zalecane
Postępuj zgodnie z zasadami niekierunkowego przepływu danych (UDF), gdzie modele View Modele ujawniają stan interfejsu użytkownika za pomocą wzorca obserwatora i otrzymują działania z interfejsu za pomocą wywołań metod.
Używaj modeli widoków AAC, jeśli ich zalety dotyczą Twojej aplikacji.
Zdecydowanie zalecane
Modele ViewModels AAC służą do obsługi logiki biznesowej i pobierania danych aplikacji w celu ujawniania stanu UI w interfejsie (widoku tworzenia lub widoków Androida).

Więcej sprawdzonych metod korzystania z ViewModel znajdziesz tutaj.

Poznaj zalety modeli ViewModels.

Używaj zbierania informacji o stanie interfejsu z uwzględnieniem cyklu życia.
Zdecydowanie zalecane
Zbieraj informacje o stanie interfejsu z poziomu interfejsu za pomocą odpowiedniego konstruktora sterowników identyfikujących cykl życia: repeatOnLifecycle w systemie Widok i collectAsStateWithLifecycle w Jetpack Compose.

Dowiedz się więcej o repeatOnLifecycle.

Dowiedz się więcej o collectAsStateWithLifecycle.

Nie wysyłaj zdarzeń z modelu ViewModel do interfejsu użytkownika.
Zdecydowanie zalecane
Natychmiast przetwórz zdarzenie w modelu ViewModel i wywołaj aktualizację stanu w wyniku obsługi tego zdarzenia. Więcej informacji o zdarzeniach związanych z interfejsem użytkownika
używać aplikacji mającej pojedynczą aktywność;
Zalecane
Używaj funkcji Fragmenty nawigacyjne lub Tworzenie wiadomości w nawigacji, aby przechodzić między ekranami, a jeśli aplikacja ma więcej niż 1 ekran, korzystaj z precyzyjnego linku do aplikacji.
korzystać z usługi Jetpack Compose,
Zalecane
Korzystaj z usługi Jetpack Compose, aby tworzyć nowe aplikacje na telefony, tablety, urządzenia składane i Wear OS.

Ten fragment kodu pokazuje, jak rejestrować stan interfejsu użytkownika w sposób odzwierciedlający cykl życia:

Wyświetlenia

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

Utwórz

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

ViewModel

Obiekty ViewModels odpowiadają za określanie stanu interfejsu użytkownika i dostęp do warstwy danych. Oto kilka sprawdzonych metod dotyczących modeli widoków:

Rekomendacja Opis
Modele ViewModel powinny być niezależne od cyklu życia Androida.
Zdecydowanie zalecane
Modele ViewModel nie powinny zawierać odwołań do żadnych typów związanych z cyklem życia. Nie przekazuj jako zależności Activity, Fragment, Context ani Resources. Jeśli coś wymaga parametru Context w modelu ViewModel, zdecydowanie zalecamy sprawdzenie, czy znajduje się on w odpowiedniej warstwie.
Użyj korekty i przepływów.
Zdecydowanie zalecane

Model ViewModel wchodzi w interakcję z warstwami danych lub domen za pomocą:

  • Kotlin – przepływy podczas odbierania danych z aplikacji
  • suspend do wykonywania działań przy użyciu viewModelScope.
Używaj modeli widoków na poziomie ekranu.
Zdecydowanie zalecane

Nie używaj obiektów ViewModel w elementach interfejsu wielokrotnego użytku. Modeli View należy używać w:

  • kompozycyjne na poziomie ekranu,
  • aktywności/fragmentów w widoku,
  • Miejsca docelowe i wykresy podczas korzystania z nawigacji Jetpack.
W komponentach interfejsu wielokrotnego użytku używaj klas zastępujących w zwykłym stanie.
Zdecydowanie zalecane
Do obsługi złożoności komponentów interfejsu wielokrotnego użytku używaj klas posiadaczy zwykłego stanu. W ten sposób władze mogą przekazywać władze na zewnątrz i nimi zarządzać.
Nie używaj AndroidViewModel.
Zalecane
Użyj zajęć ViewModel, a nie AndroidViewModel. Klasa Application nie powinna być używana w obiekcie ViewModel. Zamiast tego przenieś zależność do interfejsu użytkownika lub warstwy danych.
Ujawniaj stan interfejsu użytkownika.
Zalecane
Obiekty ViewModele powinny udostępniać dane w interfejsie za pomocą pojedynczej właściwości o nazwie uiState. Jeśli interfejs użytkownika wyświetla wiele niepowiązanych ze sobą danych, maszyna wirtualna może wydzielić wiele właściwości stanu interfejsu.
  • uiState powinien mieć status StateFlow.
  • Jeśli dane pochodzą z innych warstw hierarchii, utwórz uiState za pomocą operatora stateIn z zasadą WhileSubscribed(5000) (przykład).
  • W prostszych przypadkach, w których nie ma strumieni danych pochodzących z warstwy danych, można użyć funkcji MutableStateFlow w postaci stałej StateFlow (przykład).
  • Możesz ustawić ${Screen}UiState jako klasę danych, która może zawierać dane, błędy i sygnały wczytywania. Jeśli poszczególne stany mają charakter wyłączny, ta klasa może być również klasą zabezpieczoną.

Ten fragment kodu pokazuje, jak udostępnić stan interfejsu użytkownika w modelu ViewModel:

@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
            )

    // ...
}

Cykl życia

Oto kilka sprawdzonych metod korzystania z cyklu życia Androida:

Rekomendacja Opis
Nie zastępuj metod cyklu życia w działaniach ani we fragmentach.
Zdecydowanie zalecane
Nie zastępuj metod cyklu życia, takich jak onResume w działaniach czy fragmentach kodu. Użyj w zamian LifecycleObserver. Jeśli aplikacja musi działać, gdy cykl życia osiągnie określoną wartość Lifecycle.State, użyj interfejsu API repeatOnLifecycle.

Ten fragment kodu pokazuje, jak wykonywać operacje w określonym stanie cyklu życia:

Wyświetlenia

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

Utwórz

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

Obsługa zależności

Poniżej znajdziesz sprawdzone metody zarządzania zależnościami między komponentami:

Rekomendacja Opis
Użyj funkcji wstrzykiwania zależności.
Zdecydowanie zalecane
W miarę możliwości korzystaj ze sprawdzonych metod dotyczących wstrzykiwania zależności, głównie z wykorzystaniem wstrzykiwania konstruktora.
W razie potrzeby ustaw zakres na komponent.
Zdecydowanie zalecane
Ustaw zakres na kontener zależności, jeśli typ zawiera zmienne, które należy udostępnić, lub ten typ jest drogi do zainicjowania i jest powszechnie używany w aplikacji.
Użyj narzędzia Hilt.
Zalecane
Użyj funkcji Hilt lub ręcznego wstrzykiwania zależności w prostych aplikacjach. Jeśli Twój projekt jest wystarczająco złożony, użyj opcji Hilt. Jeśli na przykład masz:
  • Wiele ekranów z modelami wyświetlania – integracja
  • Użycie WorkManagera – integracja
  • Zaawansowane wykorzystanie Nawigacji, np. modele wyświetleń na wykresie nawigacyjnym – integracja.

Testowanie

Oto kilka sprawdzonych metod testowania:

Rekomendacja Opis
Dowiedz się, co testować.
Zdecydowanie zalecane

Jeśli projekt nie jest mniej więcej tak prosty jak aplikacja Hello World, należy go przetestować przynajmniej przy użyciu:

  • Widoki modeli dla testów jednostkowych, w tym Flows.
  • Jednostki warstwy danych do testów jednostkowych. Są to repozytoria i źródła danych.
  • Testy nawigacji interfejsu, które przydają się jako testy regresji w CI.
Woli podszyć się po drugiej stronie.
Zdecydowanie zalecane
Więcej informacji znajdziesz w artykule Używanie podwójnej precyzji w testach w dokumentacji Androida.
Testuj StateFlows.
Zdecydowanie zalecane
Podczas testowania StateFlow:

Więcej informacji znajdziesz w przewodniku po testowaniu urządzeń z Androidem (w języku angielskim).

Modele

Podczas tworzenia modeli w aplikacjach należy stosować się do tych sprawdzonych metod:

Rekomendacja Opis
Tworzenie modelu na warstwę w złożonych aplikacjach.
Zalecane

W złożonych aplikacjach twórz nowe modele w różnych warstwach lub komponentach, jeśli ma to sens. Przeanalizuj te przykłady:

  • Zdalne źródło danych może zmapować model odbierany przez sieć na prostszą klasę obejmującą tylko dane, których potrzebuje aplikacja
  • Repozytoria mogą mapować modele DAO na prostsze klasy danych, korzystając z informacji potrzebnych w warstwie interfejsu.
  • ViewModel może uwzględniać modele warstwy danych w klasach UiState.

Konwencje nazewnictwa

Podczas nazywania bazy kodu pamiętaj o tych sprawdzonych metodach:

Rekomendacja Opis
Metody nazewnictwa.
Opcjonalny
Metody powinny być wyrażeniami typu czasowniki. Przykład: makePayment().
Właściwości nazewnictwa.
Opcjonalny
Właściwości powinny być wyrażeniem rzeczowym. Przykład: inProgressTopicSelection.
Nadawanie nazw strumieniom danych.
Opcjonalny
Gdy klasa udostępnia strumień przepływów, LiveData lub dowolnego innego strumienia, konwencja nazewnictwa to get{model}Stream(). Na przykład getAuthorStream(): Flow<Author>Jeśli funkcja zwraca listę modeli, nazwa modelu powinna być w liczbie mnogiej: getAuthorsStream(): Flow<List<Author>>
Implementacje interfejsów nadawania nazw.
Opcjonalny
Nazwy implementacji interfejsów powinny być zrozumiałe. Jeśli nie można znaleźć lepszej nazwy, zastosuj prefiks Default. Na przykład w przypadku interfejsu NewsRepository może to być OfflineFirstNewsRepository lub InMemoryNewsRepository. Jeśli nie możesz znaleźć dobrej nazwy, użyj DefaultNewsRepository. Fałszywe implementacje powinny być poprzedzone prefiksem Fake, np. FakeAuthorsRepository.