本頁提供多個架構的最佳做法和建議。採用這些最佳做法和建議,可提升應用程式的品質、穩定性和擴充性,還能更輕鬆地維護及測試應用程式。
以下最佳做法依主題分組。各自的優先順序反映了團隊的建議程度。優先順序清單如下:
- 強烈建議:除非此做法與您的做法毫無衝突,否則應採用這個做法。
- 建議:這種做法可改善應用程式。
- 選用:在某些情況下,這種做法可改善應用程式。
分層架構
建議的分層架構採用關注點分離做法,透過資料模型使用 UI,符合單一可靠資料來源的原則,並維持單向資料流。以下是分層架構的一些最佳做法:
建議 | 說明 |
---|---|
使用明確定義的資料層。 強烈建議 |
資料層會將應用程式資料公開給應用程式的其餘部分,且包含應用程式絕大多數商業邏輯。
|
使用明確定義的 UI 層。 強烈建議 |
UI 層會在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。
|
資料層應使用存放區公開應用程式資料。 強烈建議 |
UI 層中的元件 (例如可組合項、活動或 ViewModel) 不應與資料來源直接互動。資料來源如下:
|
使用協同程式和資料流。 強烈建議 |
使用協同程式和資料流,在不同層之間進行通訊。 |
使用網域層。 建議用於大型應用程式 |
如果需要重複使用多個 ViewModel 中與資料層互動的商業邏輯,或是想要簡化特定 ViewModel 的商業邏輯複雜性,則使用網域層用途 |
UI 層
UI 層的作用是在螢幕上顯示應用程式資料,且為使用者與應用程式互動的主要管道。以下是 UI 層的最佳做法:
建議 | 說明 |
---|---|
遵循單向資料流 (UDF)。 強烈建議 |
遵循單向資料流程 (UDF) 原則,其中 ViewModel 使用觀測器模式公開 UI 狀態,並透過方法呼叫從 UI 接收動作。 |
如果 AAC ViewModel 對您的應用程式有幫助,建議多加利用。 強烈建議 |
使用 AAC ViewModel 處理商業邏輯,並擷取應用程式資料,以便在 UI 中公開 UI 狀態 (Compose 或 Android 檢視畫面)。 請參閱這裡的 ViewModel 最佳做法瞭解詳情。 請參閱這裡的 ViewModel 優點。 |
使用生命週期感知方法收集 UI 狀態。 強烈建議 |
使用適合的生命週期感知協同程式建構工具,從 UI 中收集 UI 狀態︰repeatOnLifecycle (View 系統) 和 collectAsStateWithLifecycle (Jetpack Compose)進一步瞭解 詳情請參閱 |
請勿將事件從 ViewModel 傳送至 UI。 強烈建議 |
在 ViewModel 中立即處理事件,並引起狀態更新,進而處理事件。請參閱這裡的 UI 事件瞭解詳情。 |
使用單一活動應用程式。 建議 |
如果應用程式有多個畫面,請使用導覽片段或導覽 Compose 前往不同畫面,以及深層連結至應用程式。 |
使用 Jetpack Compose。 建議 |
使用 Jetpack Compose 建構適用於手機、平板電腦、折疊式裝置和 Wear OS 的新應用程式。 |
下列文字片段說明如何以生命週期感知方式收集 UI 狀態:
檢視畫面
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
}
}
}
}
}
Compose
@Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
ViewModel
ViewModel 負責提供 UI 狀態和資料層存取權。以下是一些 ViewModel 最佳做法:
建議 | 說明 |
---|---|
ViewModel 應不受 Android 生命週期的影響。 強烈建議 |
ViewModel 不應保留任何生命週期相關類型的參照。不要將 Activity, Fragment, 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
)
// ...
}
生命週期
以下是使用 Android 生命週期的一些最佳做法:
建議 | 說明 |
---|---|
不要覆寫活動或片段中的生命週期方法。 強烈建議 |
不要覆寫活動或片段中的生命週期方法,例如 onResume 。請改用 LifecycleObserver 。如果應用程式在生命週期達到特定 Lifecycle.State 時需要執行作業,請使用 repeatOnLifecycle API。 |
下列文字片段概述了如何根據特定生命週期狀態來執行作業:
檢視畫面
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) {
// ...
}
}
}
}
Compose
@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)
}
}
}
處理依附元件
管理元件之間的依附元件時,應遵守下列幾項最佳做法:
建議 | 說明 |
---|---|
使用依附元件插入。 強烈建議 |
使用依附元件插入最佳做法,主要是建構函式插入 (如適用)。 |
視需要將範圍限定為元件。 強烈建議 |
如果類型包含需要共用的可變動資料,或是類型初始化成本高昂並廣泛用於應用程式,則可將範圍限定為依附元件容器。 |
使用 Hilt。 建議 |
在簡易應用程式中使用 Hilt 或手動依附元件插入。如果您的專案夠複雜,請使用 Hilt。舉例來說,如果出現以下情形:
|
測試
以下是測試的一些最佳做法:
建議 | 說明 |
---|---|
瞭解測試項目。 強烈建議 |
除非專案大致與 hello world 應用程式一樣,否則請至少測試以下項目:
|
假的實作優先於模擬。 強烈建議 |
詳情請參閱在 Android 說明文件中使用測試替身。 |
測試 StateFlows。 強烈建議 |
測試 StateFlow 時:
|
詳情請參閱 Android DAC 指南中的測試項目。
模型
在應用程式中開發模型時,您應觀察下列最佳做法:
建議 | 說明 |
---|---|
在複雜的應用程式中,為每層建立模型。 建議 |
請視需要在複雜的應用程式中,以不同的層或元件建立新模型。請見以下範例:
|
命名慣例
為程式碼集命名時,您應瞭解下列最佳做法:
建議 | 說明 |
---|---|
命名方法。 選用 |
方法應為動詞片語。例如:makePayment() 。 |
為屬性命名。 選用 |
屬性必須為名詞片語。例如:inProgressTopicSelection 。 |
為資料串流命名。 選用 |
類別公開資料串流、LiveData 或任何其他串流時,命名慣例為 get{model}Stream() 。例如,getAuthorStream(): Flow<Author>
如果函式會傳回模型清單,則模型名稱應採用複數形式:getAuthorsStream(): Flow<List<Author>> |
為介面實作命名。 選用 |
介面實作的名稱應具有意義。如果找不到更合適的名稱,請將 Default 做為前置字串。舉例來說,針對 NewsRepository 介面,您可以使用 OfflineFirstNewsRepository 或 InMemoryNewsRepository 。如果找不到好的名稱,請使用 DefaultNewsRepository 。假的實作應將 Fake 做為前置字串,如 FakeAuthorsRepository 中所示 |