本頁提供多個架構的最佳做法和建議。採用這些最佳做法和建議,可提升應用程式的品質、穩定性和擴充性,還能更輕鬆地維護及測試應用程式。
以下最佳做法依主題分組。各自的優先順序反映了建議程度。優先順序清單如下:
- 強烈建議:除非此做法與您的方法相衝突,否則應採用此做法。
- 建議:這種做法可改善應用程式。
- 選用:在某些情況下,這種做法可改善應用程式。
分層架構
建議的分層架構採用關注點分離做法,透過資料模型使用 UI,符合單一可靠資料來源的原則,並維持單向資料流。以下是分層架構的一些最佳做法:
| 建議 | 說明 |
|---|---|
| 使用明確定義的資料層。 強烈建議 |
資料層會將應用程式資料公開給應用程式的其餘部分,且包含應用程式絕大多數商業邏輯。
|
| 使用明確定義的 UI 層。 強烈建議 |
UI 層會在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。Jetpack Compose 是建構應用程式 UI 的推薦新型工具包。
|
| 使用存放區公開資料層中的應用程式資料。 強烈建議 |
請確保 UI 層中的元件 (例如可組合項或 ViewModel) 不會與資料來源直接互動。資料來源範例如下:
|
| 使用協同程式和資料流。 強烈建議 |
使用協同程式和資料流,在不同層之間進行通訊。 如要進一步瞭解協同程式最佳做法,請參閱「Android 協同程式的最佳做法」。 |
| 使用網域層。 建議用於大型應用程式 |
如果需要重複使用多個 ViewModel 中與資料層互動的商業邏輯,或是想要簡化特定 ViewModel 的商業邏輯複雜性,則使用網域層用途 |
UI 層
UI 層的作用是在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。以下是 UI 層的最佳做法:
| 建議 | 說明 |
|---|---|
| 遵循單向資料流 (UDF)。 強烈建議 |
遵循單向資料流程 (UDF) 原則,其中 ViewModel 使用觀測器模式公開 UI 狀態,並透過方法呼叫從 UI 接收動作。 |
| 如果 AAC ViewModel 對您的應用程式有幫助,建議多加利用。 強烈建議 |
使用 AAC ViewModel 處理商業邏輯,並擷取應用程式資料,以便在 UI 中公開 UI 狀態。 如要進一步瞭解 ViewModel 最佳做法,請參閱「架構建議」。 如要進一步瞭解 ViewModel 的優點,請參閱「使用 ViewModel 做為商業邏輯狀態容器」。 |
| 使用生命週期感知方法收集 UI 狀態。 強烈建議 |
使用適合的生命週期感知協同程式建構工具 collectAsStateWithLifecycle,從 UI 中收集 UI 狀態。進一步瞭解 |
| 請勿將事件從 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 中,請勿保留任何生命週期相關類型的參照。請勿將 Activity、Context 或 Resources 做為依附元件傳遞。如果 ViewModel 中有任何項目需要 Context,請仔細評估項目是否位於正確的層。 |
| 使用協同程式和資料流。 強烈建議 |
ViewModel 透過以下方式與資料或網域層互動:
|
| 在畫面層級使用 ViewModel。 強烈建議 |
不要在可重複使用的 UI 中使用 ViewModel,您應在以下位置使用 ViewModel:
|
| 在可重複使用的 UI 元件中使用純狀態容器類別。 強烈建議 |
使用純狀態狀態容器類別,處理可重複使用的複雜 UI 元件,這樣就能在外部提升及控制狀態。 |
不要使用 AndroidViewModel。建議 |
使用 ViewModel 類別,而非 AndroidViewModel。請勿在 ViewModel 中使用 Application 類別,請改為將依附元件移至 UI 或資料層。 |
| 公開 UI 狀態。 建議 |
請讓 ViewModel 透過名為 uiState 的單一屬性,向 UI 公開資料。如果 UI 顯示多個不相關的資料,VM 可能會公開多個 UI 狀態屬性。
|
以下文字片段概述了如何從 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 生命週期回呼。強烈建議 |
請勿覆寫
|
下列文字片段概述了如何根據特定生命週期狀態來執行作業:
@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。舉例來說,如果專案包含下列任一項目:
|
測試
以下是測試的一些最佳做法:
| 建議 | 說明 |
|---|---|
| 瞭解測試項目。 強烈建議 |
除非專案與「Hello World」應用程式一樣簡單,否則請測試專案。至少應包含下列項目:
|
| 假的實作優先於模擬。 強烈建議 |
如要進一步瞭解如何使用模擬物件,請參閱「在 Android 中使用測試替身」。 |
| 測試 StateFlows。 強烈建議 |
測試 StateFlow 時,請按照下列步驟操作:
|
詳情請參閱「Android 測試項目」和「測試 Compose 版面配置」。
模型
在應用程式中開發模型時,請遵守下列最佳做法:
| 建議 | 說明 |
|---|---|
| 在複雜的應用程式中,為每層建立模型。 建議 |
請視需要在複雜的應用程式中,以不同的層或元件建立新模型。請見以下範例:
|
命名慣例
為程式碼集命名時,您應瞭解下列最佳做法:
| 建議 | 說明 |
|---|---|
| 命名方法。 選用 |
使用動詞片語命名方法,例如 makePayment()。 |
| 為屬性命名。 選用 |
使用名詞片語為屬性命名,例如 inProgressTopicSelection。 |
| 為資料串流命名。 選用 |
類別公開資料流或任何其他串流時,命名慣例為 get{model}Stream。例如:getAuthorStream(): Flow<Author>。
如果函式會傳回模型清單,請使用複數模型名稱:getAuthorsStream(): Flow<List<Author>>。 |
| 為介面實作命名。 選用 |
介面實作的名稱應具有意義。如果找不到更合適的名稱,請將 Default 做為前置字串。舉例來說,針對 NewsRepository 介面,您可以使用 OfflineFirstNewsRepository 或 InMemoryNewsRepository。如果找不到好的名稱,請使用 DefaultNewsRepository。
假的實作應將 Fake 做為前置字串,如 FakeAuthorsRepository 中所示。 |
其他資源
如要進一步瞭解 Android 架構,請參閱下列其他資源: