ViewModel 總覽 Android Jetpack 的一部分。
ViewModel 類別是商業邏輯或畫面層級的狀態容器。這會向 UI 公開狀態,並封裝相關的商業邏輯。其主要優點在於可快取狀態,並透過設定變更保留。因此,在活動之間導覽或執行設定變更 (例如旋轉畫面) 時,UI 不必再次擷取資料。
如要進一步瞭解狀態容器,請參閱狀態容器指南。同樣,如要進一步瞭解 UI 層,請參閱 UI 層指南。
ViewModel 優點
ViewModel 的替代方案為純類別,可保留 UI 中顯示的資料。在活動或 Navigation 到達網頁之間導覽時,可能會發生問題。如未使用儲存執行個體狀態機制來儲存資料,則系統會將其刪除。ViewModel 提供能持續保留資料的便捷 API,可以解決此問題。
此外,對於純狀態持有者,Compose 提供 retain 功能,可讓純類別在設定變更後繼續保留,不必使用 ViewModel 的完整基礎架構。雖然這兩種機制都有助於保留狀態,但一般來說,將 ViewModel 提供給保留的執行個體會比反過來更安全,因為兩者的生命週期和清除行為不同。
ViewModel 類別有兩項主要優點:
- 可讓您保留 UI 狀態。
- 提供商業邏輯存取。
持續性
透過 ViewModel 保留的狀態和觸發的作業,ViewModel 可持續保留資料。有了這項快取功能,當發生螢幕旋轉等常見的設定變更時,就不必再次擷取資料。
範圍
將 ViewModel 執行個體化時,必須傳遞一個實作 ViewModelStoreOwner 介面的物件。這可以是 Navigation 到達網頁、Navigation 圖表、活動,或實作介面的任何其他類型。您也可以使用 rememberViewModelStoreOwner API,直接將 ViewModel 範圍限定在可組合函式。ViewModel 的範圍則會限定於 ViewModelStoreOwner 的 生命週期內。因此會保留在記憶體中,直到其 ViewModelStoreOwner 永久消失 (例如可組合項擁有者離開組合時)。
各種類別包括 ViewModelStoreOwner 介面的直接或間接子類別。直接子類別為 ComponentActivity 和 NavBackStackEntry。如需間接子類別的完整清單,請參閱 ViewModelStoreOwner 參考資料。如要將 ViewModel 範圍限定為 LazyList 或 Pager 中的個別項目,請使用 rememberViewModelStoreProvider() 將擁有者管理作業提升至父項。
當主機活動發生設定變更時,ViewModel 中的非同步工作會繼續執行,無論 ViewModel 的範圍是活動或特定可組合函式。這是要保留的金鑰。
詳情請參閱下方的「ViewModel 生命週期」一節、「ViewModel 範圍設定 API」,以及 Jetpack Compose 的狀態提升指南。
SavedStateHandle
SavedStateHandle 不僅可在設定變更期間保留資料,在程序終止期間亦是如此。因此,即使使用者關閉應用程式,稍後再次開啟,您仍然可以保持 UI 狀態。
如要進一步瞭解如何儲存 UI 狀態,請參閱「在 Compose 中儲存 UI 狀態」。
商業邏輯存取
雖然絕大部分商業邏輯都保留在資料層,但 UI 層也可以包含商業邏輯。這類情況包括合併多個存放區的資料來建立螢幕 UI 狀態,或特定類型的資料不需要資料層時。
ViewModel 是處理 UI 層中商業邏輯的適當位置。ViewModel 還負責處理事件,並在需要套用商業邏輯以修改應用程式資料時,將事件委派給階層的其他層。
實作 ViewModel
以下是使用者擲骰子畫面的 ViewModel 實作範例。
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
接著,您可以從畫面層級可組合函式存取 ViewModel,如下所示:
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
將協同程式與 ViewModel 搭配使用
ViewModel 包括支援 Kotlin 協同程式。能夠以與保留 UI 狀態相同的方式保留非同步工作。
詳情請參閱「Kotlin 協同程式與 Android 架構元件搭配使用」。
ViewModel 的生命週期
ViewModel 的生命週期與範圍具有直接關聯。ViewModel 會保留在記憶體中,直到限定範圍的 ViewModelStoreOwner 消失為止。這可能會在以下情境中發生:
- 若是活動,則會在完成時發生。
- 若是 Navigation 項目,則從返回堆疊中移除時。
- 若是可組合函式,則會在離開組合時。您可以使用
rememberViewModelStoreOwner,將 ViewModel 直接限定為 UI 的任意部分 (例如Pager或LazyList)。
因此,ViewModels 非常適合用於儲存在設定變更後仍然有效的資料。
圖 1 說明活動在進行旋轉然後完成時的不同生命週期狀態。上圖也在關聯的活動生命週期旁顯示 ViewModel 的生命週期。這張特殊圖表說明活動的狀態。

系統首次呼叫活動物件的 onCreate() 方法時,您通常會要求 ViewModel。在活動的整個生命週期中 (例如裝置畫面旋轉時),系統可能會多次呼叫 onCreate()。ViewModel 會在您首次要求 ViewModel 時存在,直到活動完成並刪除為止。
清除 ViewModel 依附元件
當 ViewModelStoreOwner 在生命週期內刪除 ViewModel 時,ViewModel 會呼叫 onCleared 方法。如此一來,您就能清理以 ViewModel 生命週期為依據的所有工作或依附元件。
以下範例為 viewModelScope 的替代方案。viewModelScope 是內建的 CoroutineScope,會自動遵循 ViewModel 生命週期。ViewModel 會根據這個範圍觸發業務相關作業。如果您想使用自訂範圍 (而非 viewModelScope) 來簡化測試程序,ViewModel 可以接收 CoroutineScope 做為建構函式中的依附元件。一旦 ViewModelStoreOwner 在生命週期結束時清除 ViewModel,ViewModel 也會取消 CoroutineScope。
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
在 Lifecycle 2.5 以上版本中,您可以傳遞一或多個 Closeable 物件到 ViewModel 的建構函式,該函式會在 ViewModel 例項清除時自動關閉。
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
最佳做法
以下是實作 ViewModel 時,應遵循的幾項主要最佳做法:
- 由於範圍限定,請使用 ViewModel 做為畫面層級狀態容器的實作詳細資料。若是方塊群組或表單等可重複使用的 UI 元件,切勿將 ViewModel 用做這類元件的狀態容器。否則,在相同的 ViewModelStoreOwner 下,同一個 UI 元件的不同用法中,會取得相同的 ViewModel 執行個體,除非您為每個晶片使用明確的檢視畫面模型鍵。
- ViewModel 不應瞭解 UI 實作詳細資料。請盡可能保留 ViewModel API 公開的方法名稱,以及 UI 狀態欄位的名稱。這樣一來,ViewModel 就能支援任何類型的 UI:手機、摺疊式裝置、平板電腦,甚至是 Chromebook!
- 由於 ViewModel 的存續時間可能比
ViewModelStoreOwner長,因此 ViewModel 不應保留生命週期相關 API (例如Context或Resources) 的任何參照,以免發生記憶體流失。 - 切勿將 ViewModel 傳遞給其他類別、函式或 UI 元件。由於平台會管理這些元件,因此請盡可能保持靠近,也就是靠近您的 Activity、畫面層級可組合函式或 Navigation 目的地。這樣可以防止較低層級的元件,存取不必要的資料和邏輯。
其他資訊
隨著資料越趨複雜,您可能會選擇使用獨立的類別來載入資料。ViewModel 的用途是封裝 UI 控制器的資料,讓資料於設定變更後仍然有效。如要瞭解如何在設定變更時載入、保留及管理資料,請參閱「儲存 UI 狀態」。
「Android 應用程式架構指南」建議建立存放區類別,以處理這些功能。
其他資源
如要進一步瞭解 ViewModel 類別,請參閱下列資源。
說明文件
Views content
範例
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 搭配生命週期感知元件使用 Kotlin 協同程式
- 儲存 UI 狀態
- 載入並顯示分頁資料