ViewModel 總覽 Android Jetpack 的一部分。

ViewModel 類別是商業邏輯或畫面層級的狀態 holder。這會向 UI 公開狀態,並封裝相關的商業邏輯。 其主要優點在於可快取狀態,並透過 設定變更。這表示 UI 不必再次擷取資料 (例如切換各項活動或遵循設定變更時,例如 請旋轉一下

如要進一步瞭解狀態容器,請參閱狀態容器指南。 同樣,如要進一步瞭解 UI 層,請參閱 UI 層 指引。

ViewModel 優點

ViewModel 的替代方案為純類別,可保留 UI 中顯示的資料。在活動或不同活動之間導覽時,可能會發生問題 導覽目的地。如未儲存,系統會將其刪除 來利用儲存執行個體狀態機制。ViewModel 提供能持續保留資料的便捷 API,可以解決此問題。

ViewModel 類別有兩項主要優點:

  • 持續保留 UI 狀態。
  • 提供商業邏輯存取。
,瞭解如何調查及移除這項存取權。

持續保留資料

透過 ViewModel 保留的狀態和觸發的作業,ViewModel 可持續保留資料。有了這項快取功能,當發生螢幕旋轉等常見的設定變更時,就不必再次擷取資料。

範圍

將 ViewModel 執行個體化時,必須傳遞一個實作 ViewModelStoreOwner 介面。這可以是 Navigation 目的地 導覽圖、活動、片段或任何其他實作 存取 APIViewModel 的範圍則會限定於 ViewModelStoreOwner。因此會保留在記憶體中,直到其 ViewModelStoreOwner 為止 就會永久消失

各種類別包括 ViewModelStoreOwner 介面。直接子類別 ComponentActivityFragmentNavBackStackEntry。 如需間接子類別的完整清單,請參閱 ViewModelStoreOwner 參考資料

當 ViewModel 範圍內的片段或活動遭到刪除時,非同步工作會繼續在 ViewModel 範圍內執行。這是 保持金鑰的保留性。

詳情請參閱下方「ViewModel 生命週期」一節。

SavedStateHandle

SavedStateHandle 不僅可透過設定保留資料 但會在重新建立程序期間發生變更也就是說,只要保留 即使使用者關閉應用程式,稍後再次開啟時也是如此。

商業邏輯存取

即使資料中有絕大部分商業邏輯 層,UI 層也可以包含商業邏輯。這類情況包括合併多個存放區的資料來建立螢幕 UI 狀態,或特定類型的資料不需要資料層時。

ViewModel 適合處理 UI 層中的商業邏輯。 ViewModel 也負責處理事件並委派給其他 才能修改商業邏輯 應用程式資料

Jetpack Compose

使用 Jetpack Compose 時,ViewModel 是顯示畫面 UI 的主要方式 新增至可組合函式。在混合式應用程式中,活動和片段只會代管 可組合函式這與過去的方法不同,過去建立包含活動和片段的可重複使用 UI 時,較不簡單直觀,導致 UI 遠比 UI 控制器更活躍。

請務必留意,搭配使用 ViewModel 與 Compose 時,不可將 ViewModel 的範圍限定為可組合函式。這是因為 不是 ViewModelStoreOwner。中同一個可組合函式的兩個執行個體 組成,或兩個存取相同 ViewModel 類型的不同可組合函式 相同的 ViewModelStoreOwner 下會接收「同一個」例項 ViewModel,這通常不是預期行為。

為了在 Compose 中獲得 ViewModel 的優點,請在 Fragment 中代管每個畫面 或 Activity,或使用 Compose Navigation,並在可組合函式中使用 ViewModel 函式。這是因為 ViewModel 的範圍可限定為導覽目的地、導覽圖、活動及片段。

詳情請參閱 Jetpack Compose 的狀態提升指南。

實作 ViewModel

以下是使用者擲骰子畫面的 ViewModel 實作範例。

Kotlin

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

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

接著,您可以從活動中存取 ViewModel,如以下程式碼片段所示:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

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 項目,則從返回堆疊中移除時。

因此,ViewModels 非常適合用於儲存仍然存在的資料 設定變更。

圖 1 說明活動在進行旋轉然後完成時的不同生命週期狀態。上圖也顯示 ViewModel,在相關聯的活動生命週期旁。這項特定 圖表說明活動的狀態同樣的基本狀態適用於 片段的生命週期

說明在活動變更狀態下的 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 用做這類元件的狀態容器。否則,也會 在相同 UI 元件中,以不同方式使用 ViewModel 例項 除非您為每個方塊使用明確的檢視模型金鑰,否則 ViewModelStoreOwner 除外。
  • ViewModel 不應瞭解 UI 實作詳細資料。保留名稱 ViewModel API 公開的方法,以及 UI 狀態欄位的資訊 通用這樣一來,ViewModel 就能支援任何類型的 UI:手機、摺疊式裝置、平板電腦,甚至是 Chromebook!
  • 存續時間可能比 ViewModelStoreOwner 長,因此 ViewModel 不應保留生命週期相關 API 的任何參照,例如 ContextResources 以避免記憶體流失。
  • 切勿將 ViewModel 傳遞給其他類別、函式或 UI 元件。由於平台會管理這些元件,因此請盡量保持靠近 。靠近活動、片段或畫面層級可組合函式。 這樣可以防止較低層級的元件,存取高於 他們的需求

其他資訊

隨著資料越趨複雜,您可能會選擇使用獨立的類別來載入資料。ViewModel 的用途是封裝 使用者介面控制器,讓資料在設定變更後仍然有效。資訊 瞭解如何在設定變更時載入、保留及管理資料,請參閱 已儲存 UI 狀態

Android 應用程式架構指南」建議建立存放區類別 處理這些函式

其他資源

如要進一步瞭解 ViewModel 類別,請參閱下列資源 再複習一下,機構節點 是所有 Google Cloud Platform 資源的根節點

說明文件

範例