Zalecenia dotyczące architektury Androida

Na tej stronie znajdziesz kilka sprawdzonych metod i zaleceń dotyczących architektury. Wprowadź je, aby poprawić jakość, niezawodność i skalowalność aplikacji. Ułatwiają też utrzymywanie i testowanie aplikacji.

Poniższe sprawdzone metody są pogrupowane według tematu. Każda z nich ma priorytet, który odzwierciedla siłę rekomendacji. Lista priorytetów jest następująca:

  • Zdecydowanie zalecane: wdróż tę metodę, chyba że jest ona sprzeczna z Twoim podejściem.
  • Zalecane: ta praktyka prawdopodobnie poprawi jakość Twojej aplikacji.
  • Opcjonalnie: w określonych okolicznościach może to poprawić jakość aplikacji.

Architektura warstwowa

Zalecana przez nas architektura warstwowa sprzyja rozdzieleniu zadań. Interfejs użytkownika jest tworzony na podstawie modeli danych, jest zgodny z zasadą jednego źródła wiarygodnych informacji i z zasadami jednokierunkowego przepływu danych. Oto kilka sprawdzonych metod dotyczących architektury warstwowej:

Rekomendacja Opis
Używaj jasno określonej warstwy danych. Warstwa danych udostępnia dane aplikacji pozostałym jej częściom i zawiera większość logiki biznesowej aplikacji.
  • Twórz repozytoria, nawet jeśli zawierają tylko jedno źródło danych.
  • W przypadku małych aplikacji możesz umieścić typy warstwy danych w pakiecie lub module data.
Używaj wyraźnie zdefiniowanej warstwy interfejsu. Warstwa interfejsu wyświetla dane aplikacji na ekranie i służy jako główny punkt interakcji użytkownika. Jetpack Compose to zalecany nowoczesny zestaw narzędzi do tworzenia interfejsu aplikacji.
  • W przypadku małych aplikacji możesz umieścić typy warstwy danych w pakiecie lub module ui.
Więcej informacji o sprawdzonych metodach dotyczących warstwy interfejsu znajdziesz w artykule Warstwa interfejsu.
Udostępnij dane aplikacji z warstwy danych za pomocą repozytorium.

Upewnij się, że komponenty w warstwie interfejsu, takie jak funkcje kompozycyjne czy modele ViewModel, nie wchodzą w bezpośrednią interakcję ze źródłem danych. Przykłady źródeł danych:

  • Bazy danych, DataStore, SharedPreferences, interfejsy API Firebase.
  • dostawców lokalizacji GPS;
  • dostawców danych Bluetooth;
  • dostawców stanu połączenia sieciowego;
Używaj współprogramów i przepływów. Do komunikacji między warstwami używaj korutyn i przepływów.

Więcej informacji o sprawdzonych metodach dotyczących współprogramów znajdziesz w artykule Sprawdzone metody dotyczące współprogramów na Androidzie.

Użyj warstwy domeny. Używaj warstwy domeny w przypadku przypadków użycia, w których musisz ponownie wykorzystać logikę biznesową, która wchodzi w interakcję z warstwą danych w wielu modelach ViewModel, lub gdy chcesz uprościć złożoność logiki biznesowej konkretnego modelu ViewModel.

Warstwa interfejsu

Warstwa interfejsu służy do wyświetlania danych aplikacji na ekranie i jest głównym punktem interakcji użytkownika. Oto kilka sprawdzonych metod dotyczących warstwy interfejsu:

Rekomendacja Opis
Postępuj zgodnie z zasadami jednokierunkowego przepływu danych (UDF). Postępuj zgodnie z zasadami jednokierunkowego przepływu danych (UDF), w którym obiekty ViewModel udostępniają stan interfejsu za pomocą wzorca obserwatora i otrzymują działania z interfejsu za pomocą wywołań metod.
Używaj AAC ViewModels, jeśli ich zalety mają zastosowanie w Twojej aplikacji. Używaj AAC ViewModels do obsługi logiki biznesowej i pobierania danych aplikacji, aby udostępniać interfejsowi stan interfejsu.

Więcej informacji o sprawdzonych metodach dotyczących ViewModel znajdziesz w artykule Zalecenia dotyczące architektury.

Więcej informacji o zaletach ViewModels znajdziesz w artykule ViewModel jako kontener stanu logiki biznesowej.

Używaj zbierania stanu interfejsu opartego na cyklu życia. Zbieraj stan interfejsu z interfejsu za pomocą odpowiedniego konstruktora współprogramów uwzględniającego cykl życia, collectAsStateWithLifecycle.

Więcej informacji o collectAsStateWithLifecycle.

Nie wysyłaj zdarzeń z ViewModel do interfejsu. Natychmiast przetwórz zdarzenie w obiekcie ViewModel i spowoduj aktualizację stanu z wynikiem obsługi zdarzenia. Więcej informacji o zdarzeniach interfejsu znajdziesz w artykule Obsługa zdarzeń ViewModel.
Używaj aplikacji z jedną aktywnością. Użyj komponentu Navigation 3, aby przechodzić między ekranami i precyzyjnie linkować do aplikacji, jeśli ma ona więcej niż 1 ekran.
Używaj Jetpack Compose. Używaj Jetpack Compose do tworzenia nowych aplikacji na telefony, tablety, urządzenia składane i zegarki z Wear OS.

Poniższy fragment kodu pokazuje, jak zbierać stan interfejsu w sposób uwzględniający cykl życia:

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

ViewModel

ViewModele odpowiadają za udostępnianie stanu interfejsu i dostęp do warstwy danych. Oto kilka sprawdzonych metod dotyczących klas ViewModel:

Rekomendacja Opis
Zachowaj niezależność obiektów ViewModel od cyklu życia Androida. W klasach ViewModel nie przechowuj odwołań do żadnych typów związanych z cyklem życia. Nie przekazuj Activity, Context ani Resources jako zależności. Jeśli coś wymaga Context w ViewModelu, dokładnie sprawdź, czy jest to odpowiednia warstwa.
Używaj współprogramów i przepływów.

ViewModel wchodzi w interakcję z warstwami danych lub domen za pomocą tych elementów:

  • Strumienie Kotlin do odbierania danych aplikacji
  • Funkcje suspend do wykonywania działań za pomocą viewModelScope
Używaj obiektów ViewModel na poziomie ekranu.

Nie używaj klas ViewModel w elementach interfejsu, których można używać wielokrotnie. Modeli ViewModel należy używać w tych przypadkach:

  • Komponenty na poziomie ekranu
  • miejsc docelowych lub wykresów podczas korzystania z nawigacji Jetpack,

W przypadku bardziej złożonych funkcji kompozycyjnych lub tych, których działanie dynamicznie zmienia się w zależności od stanu, użyj rememberViewModelStoreOwner(), aby określić zakres ViewModel bezpośrednio w miejscu wywołania funkcji kompozycyjnej.

Używaj zwykłych klas przechowujących stan w komponentach interfejsu do wielokrotnego użytku. Używaj zwykłych klas przechowujących stan do obsługi złożoności w komponentach interfejsu do wielokrotnego użytku. W takim przypadku stan może zostać przeniesiony i sterowany zewnętrznie.
Nie używaj AndroidViewModel. Użyj klasy ViewModel, a nie AndroidViewModel. Nie używaj klasy Application w obiekcie ViewModel. Zamiast tego przenieś zależność do interfejsu lub warstwy danych.
Udostępnij stan interfejsu. Spraw, aby obiekty ViewModel udostępniały dane interfejsowi za pomocą jednej właściwości o nazwie uiState. Jeśli interfejs wyświetla wiele niepowiązanych ze sobą danych, maszyna wirtualna może udostępniać wiele właściwości stanu interfejsu.
  • Zmień uiState w StateFlow.
  • Utwórz uiState za pomocą operatora stateIn z zasadami WhileSubscribed(5000), jeśli dane pochodzą ze strumienia danych z innych warstw hierarchii. (Zobacz ten przykład kodu).
  • W prostszych przypadkach, gdy z warstwy danych nie pochodzą żadne strumienie danych, można użyć obiektu MutableStateFlow udostępnianego jako niezmienny obiekt StateFlow.
  • Możesz wybrać, aby ${Screen}UiState był klasą danych, która może zawierać dane, błędy i sygnały wczytywania. Jeśli różne stany są wykluczające się, ta klasa może być też klasą zamkniętą.

Poniższy fragment kodu pokazuje, jak udostępnić stan interfejsu z klasy 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

Stosuj sprawdzone metody dotyczące cyklu życia aktywności:

Rekomendacja Opis
Używaj w kompozycjach efektów uwzględniających cykl życia zamiast zastępować wywołania zwrotne cyklu życia Activity.

Nie zastępuj metod cyklu życia Activity, takich jak onResume, aby wykonywać zadania związane z interfejsem. Zamiast tego użyj LifecycleEffects w Compose lub zakresów współprogramów uwzględniających cykl życia:

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

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

Obsługa zależności

Podczas zarządzania zależnościami między komponentami postępuj zgodnie ze sprawdzonymi metodami:

Rekomendacja Opis
Używaj wstrzykiwania zależności. Stosuj sprawdzone metody wstrzykiwania zależności, a w miarę możliwości korzystaj głównie z wstrzykiwania przez konstruktor.
W razie potrzeby ogranicz zakres do komponentu. Ogranicz zakres do kontenera zależności, gdy typ zawiera modyfikowalne dane, które muszą być udostępniane, lub gdy typ jest kosztowny w inicjalizacji i jest szeroko stosowany w aplikacji.
Użyj Hilt. W prostych aplikacjach używaj Hilta lub ręcznego wstrzykiwania zależności. Używaj Hilt, jeśli projekt jest wystarczająco złożony, np. zawiera którykolwiek z tych elementów:
  • Wiele ekranów z obiektami ViewModel
  • Korzystanie z biblioteki WorkManager
  • ma obiekty ViewModel powiązane z elementami na stosie wstecznym nawigacji;

Testowanie

Oto kilka sprawdzonych metod dotyczących testowania:

Rekomendacja Opis
Wiedzieć, co testować

Jeśli projekt nie jest tak prosty jak aplikacja „hello world”, przetestuj go. Powinny one zawierać co najmniej te informacje:

  • Testy jednostkowe dla ViewModeli, w tym Flow
  • testy jednostkowe dla elementów warstwy danych, czyli repozytoriów i źródeł danych;
  • Testy nawigacji w interfejsie, które są przydatne jako testy regresyjne w CI
  • Testy wydajnościowe z użyciem Macrobenchmark do pomiaru uruchamiania aplikacji i renderowania interfejsu
Preferuj obiekty zastępcze zamiast atrap. Więcej informacji o używaniu obiektów zastępczych znajdziesz w artykule Używanie obiektów zastępczych w Androidzie.
Testowanie StateFlows. Podczas testowania StateFlow wykonaj te czynności:

Więcej informacji znajdziesz w artykułach Co testować na Androidzie i Testowanie układu Compose.

Wydajność

Oto kilka sprawdzonych metod poprawy skuteczności:

Rekomendacja Opis
Użyj R8, aby zmniejszyć rozmiar aplikacji i ją zoptymalizować. Włącz R8 w trybie pełnym w konfiguracji kompilacji, aby usunąć nieużywany kod, zaciemnić klasy i przepisać kod w celu zwiększenia wydajności w czasie działania.
wdrożyć profile podstawowe i profile uruchamiania; Generuj profile podstawowe i profile uruchamiania, aby wstępnie skompilować krytyczne ścieżki kodu. Znacznie skraca to czas oczekiwania na uruchomienie (TTID i TTFD) oraz minimalizuje zacinanie się i błędy ANR.
Testowanie wydajności za pomocą Macrobenchmark. Użyj biblioteki Macrobenchmark, aby przetestować wydajność aplikacji i zapobiec regresji.

Modele

Podczas tworzenia modeli w aplikacjach stosuj te sprawdzone metody:

Rekomendacja Opis
W przypadku złożonych aplikacji utwórz model dla każdej warstwy.

W przypadku złożonych aplikacji twórz nowe modele w różnych warstwach lub komponentach, gdy ma to sens. Oto przykłady:

  • Zdalne źródło danych może mapować model otrzymany przez sieć na prostszą klasę zawierającą tylko dane potrzebne aplikacji.
  • Repozytoria mogą mapować modele DAO na prostsze klasy danych, które zawierają tylko informacje potrzebne warstwie interfejsu.
  • ViewModel może zawierać modele warstwy danych w klasach UiState.

Konwencje nazewnictwa

Podczas nadawania nazwy bazie kodu warto skorzystać z tych sprawdzonych metod:

Rekomendacja Opis
Metody nadawania nazw.
Opcjonalnie
Do nazywania metod używaj wyrażeń czasownikowych, np. makePayment().
Nazwy właściwości.
Opcjonalnie
Do nazywania właściwości używaj wyrażeń rzeczownikowych, np. inProgressTopicSelection.
Nazewnictwo strumieni danych.
Opcjonalnie
Gdy klasa udostępnia strumień Flow lub inny strumień, obowiązuje konwencja nazewnictwa get{model}Stream, np. getAuthorStream(): Flow<Author>. Jeśli funkcja zwraca listę modeli, użyj nazwy modelu w liczbie mnogiej: getAuthorsStream(): Flow<List<Author>>.
Nazewnictwo implementacji interfejsów.
Opcjonalnie
Nadawaj implementacjom interfejsów rozpoznawalne nazwy. Jeśli nie można znaleźć lepszej nazwy, użyj prefiksu Default. Na przykład w przypadku interfejsu NewsRepository możesz mieć OfflineFirstNewsRepository lub InMemoryNewsRepository. Jeśli nie możesz znaleźć odpowiedniej nazwy, użyj DefaultNewsRepository. Dodaj prefiks Fake do fałszywych implementacji, np. FakeAuthorsRepository.

Dodatkowe materiały

Więcej informacji o architekturze Androida znajdziesz w tych materiałach:

Dokumentacja

Wyświetla treści