Android 架構建議

本頁提供多個架構的最佳做法和建議。採用這些最佳做法和建議,可提升應用程式的品質、穩定性和擴充性,還能更輕鬆地維護及測試應用程式。

以下最佳做法依主題分組。各自的優先順序反映了建議程度。優先順序清單如下:

  • 強烈建議:除非此做法與您的方法相衝突,否則應採用此做法。
  • 建議:這種做法可改善應用程式。
  • 選用:在某些情況下,這種做法可改善應用程式。

分層架構

建議的分層架構採用關注點分離做法,透過資料模型使用 UI,符合單一可靠資料來源的原則,並維持單向資料流。以下是分層架構的一些最佳做法:

建議 說明
使用明確定義的資料層 資料層會將應用程式資料公開給應用程式的其餘部分,且包含應用程式絕大多數商業邏輯。
  • 即使只有單一資料來源,仍應建立存放區
  • 在小型應用程式中,可以選擇將資料層類型放入 data 套件或模組。
使用明確定義的 UI 層 UI 層會在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。Jetpack Compose 是建構應用程式 UI 的推薦新型工具包。
  • 在小型應用程式中,可以選擇將資料層類型放入 ui 套件或模組。
如要進一步瞭解 UI 層最佳做法,請參閱「UI 層」。
使用存放區公開資料層中的應用程式資料。

請確保 UI 層中的元件 (例如可組合項或 ViewModel) 不會與資料來源直接互動。資料來源範例如下:

  • Database、DataStore、SharedPreferences、Firebase API。
  • GPS 位置提供者。
  • 藍牙資料供應商。
  • 網路連線狀態提供者。
使用協同程式和資料流 使用協同程式和資料流,在不同層之間進行通訊。

如要進一步瞭解協同程式最佳做法,請參閱「Android 協同程式的最佳做法」。

使用網域層 如果需要重複使用多個 ViewModel 中與資料層互動的商業邏輯,或是想要簡化特定 ViewModel 的商業邏輯複雜性,則使用網域層用途

UI 層

UI 層的作用是在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。以下是 UI 層的最佳做法:

建議 說明
遵循單向資料流 (UDF) 遵循單向資料流程 (UDF) 原則,其中 ViewModel 使用觀測器模式公開 UI 狀態,並透過方法呼叫從 UI 接收動作。
如果 AAC ViewModel 對您的應用程式有幫助,建議多加利用。 使用 AAC ViewModel 處理商業邏輯,並擷取應用程式資料,以便在 UI 中公開 UI 狀態。

如要進一步瞭解 ViewModel 最佳做法,請參閱「架構建議」。

如要進一步瞭解 ViewModel 的優點,請參閱「使用 ViewModel 做為商業邏輯狀態容器」。

使用生命週期感知方法收集 UI 狀態。 使用適合的生命週期感知協同程式建構工具 collectAsStateWithLifecycle,從 UI 中收集 UI 狀態。

進一步瞭解 collectAsStateWithLifecycle

請勿將事件從 ViewModel 傳送至 UI。 在 ViewModel 中立即處理事件,並引起狀態更新,進而處理事件。如要進一步瞭解 UI 事件,請參閱「處理 ViewModel 事件」。
使用單一活動應用程式。 如果應用程式有多個畫面,請使用 Navigation 3 前往不同畫面,以及深層連結至應用程式。
使用 Jetpack Compose 使用 Jetpack Compose 建構適用於手機、平板電腦、折疊式裝置和 Wear OS 的新應用程式。

下列文字片段說明如何以生命週期感知方式收集 UI 狀態:

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

ViewModel

ViewModel 負責提供 UI 狀態和存取資料層。以下是一些 ViewModel 最佳做法:

建議 說明
讓 ViewModel 獨立於 Android 生命週期。 在 ViewModel 中,請勿保留任何生命週期相關類型的參照。請勿將 ActivityContextResources 做為依附元件傳遞。如果 ViewModel 中有任何項目需要 Context,請仔細評估項目是否位於正確的層。
使用協同程式和資料流

ViewModel 透過以下方式與資料或網域層互動:

  • Kotlin 資料流,用於接收應用程式資料
  • suspend 函式,使用 viewModelScope 執行動作
在畫面層級使用 ViewModel。

不要在可重複使用的 UI 中使用 ViewModel,您應在以下位置使用 ViewModel:

  • 畫面層級可組合項
  • 檢視畫面中的活動/片段
  • 使用 Jetpack Navigation 時的到達網頁或圖表。
在可重複使用的 UI 元件中使用純狀態容器類別 使用純狀態狀態容器類別,處理可重複使用的複雜 UI 元件,這樣就能在外部提升及控制狀態。
不要使用 AndroidViewModel 使用 ViewModel 類別,而非 AndroidViewModel。請勿在 ViewModel 中使用 Application 類別,請改為將依附元件移至 UI 或資料層。
公開 UI 狀態。 請讓 ViewModel 透過名為 uiState 的單一屬性,向 UI 公開資料。如果 UI 顯示多個不相關的資料,VM 可能會公開多個 UI 狀態屬性
  • uiState 設為 StateFlow
  • 如果資料來自階層中其他層的資料串流,請使用 stateIn 運算子搭配 WhileSubscribed(5000) 政策建立 uiState。(請參閱這個程式碼範例)。
  • 如果案例較簡單,沒有任何資料串流來自資料層,則可以使用 MutableStateFlow 做為不可變更的 StateFlow 公開。
  • 您可以選擇將 ${Screen}UiState 設為資料類別,其中可包含資料、錯誤和載入信號。如果排除不同的狀態,這個類別也可以是密封類別。

以下文字片段概述了如何從 ViewModel 公開 UI 狀態:

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

    // ...
}

生命週期

遵循使用 Activity 生命週期的最佳做法:

建議 說明
在可組合函式中使用生命週期感知效果,而非覆寫 Activity 生命週期回呼。

請勿覆寫 Activity 生命週期方法 (例如 onResume),以執行與 UI 相關的工作。請改用 Compose 的 LifecycleEffects 或生命週期感知協同程式範圍:

下列文字片段概述了如何根據特定生命週期狀態來執行作業:

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

處理依附元件

管理元件之間的依附元件時,請遵循下列最佳做法:

建議 說明
使用依附元件插入 使用依附元件插入最佳做法,主要是建構函式插入 (如適用)。
視需要將範圍限定為元件。 如果類型包含需要共用的可變動資料,或是類型初始化成本高昂並廣泛用於應用程式,則可將範圍限定為依附元件容器
使用 Hilt 在簡易應用程式中使用 Hilt手動依附元件插入。如果專案夠複雜,請使用 Hilt。舉例來說,如果專案包含下列任一項目:
  • 使用 ViewModel 的多個畫面
  • 使用 WorkManager
  • 範圍限定為導覽返回堆疊的 ViewModel

測試

以下是測試的一些最佳做法:

建議 說明
瞭解測試項目

除非專案與「Hello World」應用程式一樣簡單,否則請測試專案。至少應包含下列項目:

  • ViewModel 的單元測試,包括 Flow
  • 資料層實體的單元測試,也就是存放區和資料來源
  • 實用的 UI 導覽測試,適合在持續整合 (CI) 中使用迴歸測試
假的實作優先於模擬。 如要進一步瞭解如何使用模擬物件,請參閱「在 Android 中使用測試替身」。
測試 StateFlows。 測試 StateFlow 時,請按照下列步驟操作:

詳情請參閱「Android 測試項目」和「測試 Compose 版面配置」。

模型

在應用程式中開發模型時,請遵守下列最佳做法:

建議 說明
在複雜的應用程式中,為每層建立模型。

請視需要在複雜的應用程式中,以不同的層或元件建立新模型。請見以下範例:

  • 遠端資料來源可以將其透過網路接收的模型,對應至僅包含應用程式所需資料的更簡單的類別。
  • 存放區可以將 DAO 模型,對應至僅包含 UI 層所需資訊的更簡單的資料類別。
  • ViewModel 可包含 UiState 類別中的資料層模型。

命名慣例

為程式碼集命名時,您應瞭解下列最佳做法:

建議 說明
命名方法。
選用
使用動詞片語命名方法,例如 makePayment()
為屬性命名。
選用
使用名詞片語為屬性命名,例如 inProgressTopicSelection
為資料串流命名。
選用
類別公開資料流或任何其他串流時,命名慣例為 get{model}Stream。例如:getAuthorStream(): Flow<Author>。 如果函式會傳回模型清單,請使用複數模型名稱:getAuthorsStream(): Flow<List<Author>>
為介面實作命名。
選用
介面實作的名稱應具有意義。如果找不到更合適的名稱,請將 Default 做為前置字串。舉例來說,針對 NewsRepository 介面,您可以使用 OfflineFirstNewsRepositoryInMemoryNewsRepository。如果找不到好的名稱,請使用 DefaultNewsRepository。 假的實作應將 Fake 做為前置字串,如 FakeAuthorsRepository 中所示。

其他資源

如要進一步瞭解 Android 架構,請參閱下列其他資源:

說明文件

Views content