Android 架構建議 (original) (raw)
本頁提供多個架構的最佳做法和建議。採用這些最佳做法和建議,可提升應用程式的品質、穩定性和擴充性,還能更輕鬆地維護及測試應用程式。
以下最佳做法依主題分組。各自的優先順序反映了建議程度。優先順序清單如下:
- 強烈建議:除非此做法與您的方法相衝突,否則應採用此做法。
- 建議:這種做法可改善應用程式。
- 選用:在某些情況下,這種做法可改善應用程式。
分層架構
建議的分層架構採用關注點分離做法,透過資料模型使用 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 中,請勿保留任何生命週期相關類型的參照。請勿將 Activity、Context 或 Resources 做為依附元件傳遞。如果 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 或生命週期感知協同程式範圍: 在活動開始和停止時,使用 LifecycleStartEffect 執行同步作業。活動恢復及暫停時,請使用 LifecycleResumeEffect 執行同步工作。使用 repeatOnLifecycle 執行非同步工作,以回應生命週期事件。使用 collectAsStateWithLifecycle 從 Flow 收集非同步資料。 |
下列文字片段概述了如何根據特定生命週期狀態來執行作業:
@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 時,請按照下列步驟操作: 盡可能聲明 value 屬性。 使用 WhileSubscribed。 |
詳情請參閱「Android 測試項目」和「測試 Compose 版面配置」。
模型
在應用程式中開發模型時,請遵守下列最佳做法:
| 建議 | 說明 |
|---|---|
| 在複雜的應用程式中,為每層建立模型。建議 | 請視需要在複雜的應用程式中,以不同的層或元件建立新模型。請見以下範例: 遠端資料來源可以將其透過網路接收的模型,對應至僅包含應用程式所需資料的更簡單的類別。 存放區可以將 DAO 模型,對應至僅包含 UI 層所需資訊的更簡單的資料類別。 ViewModel 可包含 UiState 類別中的資料層模型。 |
命名慣例
為程式碼集命名時,您應瞭解下列最佳做法:
| 建議 | 說明 |
|---|---|
| 命名方法。選用 | 使用動詞片語命名方法,例如 makePayment()。 |
| 為屬性命名。選用 | 使用名詞片語為屬性命名,例如 inProgressTopicSelection。 |
| 為資料串流命名。選用 | 類別公開資料流或任何其他串流時,命名慣例為 get{model}Stream。例如:getAuthorStream(): Flow。 如果函式會傳回模型清單,請使用複數模型名稱:getAuthorsStream(): Flow<List>。 |
| 為介面實作命名。選用 | 介面實作的名稱應具有意義。如果找不到更合適的名稱,請將 Default 做為前置字串。舉例來說,針對 NewsRepository 介面,您可以使用 OfflineFirstNewsRepository 或 InMemoryNewsRepository。如果找不到好的名稱,請使用 DefaultNewsRepository。 假的實作應將 Fake 做為前置字串,如 FakeAuthorsRepository 中所示。 |
其他資源
如要進一步瞭解 Android 架構,請參閱下列其他資源: