在 Android 开发中,数据的管理和状态的保存是至关重要的。ViewModel 作为 Android 架构组件的一部分,为我们提供了一种在配置更改(如屏幕旋转)时保存数据和管理 UI 状态的有效方式。而在 Android Compose 中,ViewModel 委托进一步简化了 ViewModel 的使用,使得开发者能够更加方便地在 Composable 函数中获取和使用 ViewModel。
本文将深入分析 Android Compose 框架中的 ViewModel 委托,从基础概念入手,逐步深入到源码级别,详细探讨其工作原理、使用方法、实际应用场景以及性能优化等方面。通过本文的学习,你将对 ViewModel 委托有一个全面而深入的理解,能够在实际开发中更加熟练地运用它来构建高质量的 Android 应用。
ViewModel 是 Android 架构组件中的一个类,用于存储和管理与 UI 相关的数据,并且在配置更改(如屏幕旋转)时保持数据的一致性。它的主要作用包括:
下面是一个简单的 ViewModel 使用示例:
kotlin
import androidx.lifecycle.ViewModel
// 定义一个 ViewModel 类,继承自 ViewModel
class MyViewModel : ViewModel() {
// 定义一个可变的 LiveData 对象,用于存储数据
private val _count = MutableLiveData(0)
// 提供一个只读的 LiveData 对象,供外部观察
val count: LiveData<Int> = _count
// 定义一个方法,用于增加计数器的值
fun increment() {
_count.value = _count.value?.plus(1)
}
}
在 Activity 或 Fragment 中使用该 ViewModel:
kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.example.myapp.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 使用 ViewModelProvider 获取 ViewModel 实例
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 观察 LiveData 的变化,并更新 UI
viewModel.count.observe(this) { count ->
binding.textView.text = count.toString()
}
// 点击按钮时调用 ViewModel 的方法
binding.button.setOnClickListener {
viewModel.increment()
}
}
}
在上述示例中,我们定义了一个 MyViewModel
类,其中包含一个 MutableLiveData
对象 _count
用于存储计数器的值,以及一个只读的 LiveData
对象 count
供外部观察。在 MainActivity
中,我们使用 ViewModelProvider
获取 MyViewModel
的实例,并观察 count
的变化,当 count
的值发生变化时,更新 UI。点击按钮时,调用 ViewModel
的 increment
方法增加计数器的值。
委托是 Kotlin 语言中的一个特性,它允许将一个对象的某些操作委托给另一个对象来处理。在 Kotlin 中,委托可以分为类委托和属性委托。类委托允许一个类将其部分或全部方法的实现委托给另一个对象,而属性委托则允许将属性的 getter 和 setter 方法委托给另一个对象。
ViewModel 委托是一种在 Android Compose 中使用的属性委托,它允许我们在 Composable 函数中方便地获取和使用 ViewModel。通过 ViewModel 委托,我们可以避免在 Composable 函数中手动创建和管理 ViewModel 实例,从而简化代码,提高开发效率。
viewModel
函数获取 ViewModel 实例在 Android Compose 中,我们可以使用 viewModel
函数来获取 ViewModel 实例。viewModel
函数是一个扩展函数,它定义在 androidx.lifecycle.viewmodel.compose
包中。
下面是一个简单的示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
// 定义一个 ViewModel 类
class MyViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun increment() {
_count.value = _count.value?.plus(1)
}
}
@Composable
fun MyComposable() {
// 使用 viewModel 函数获取 ViewModel 实例
val viewModel: MyViewModel = viewModel()
Column {
// 观察 LiveData 的变化,并更新 UI
viewModel.count.observeAsState().value?.let { count ->
Text(text = "Count: $count")
}
// 点击按钮时调用 ViewModel 的方法
Button(onClick = { viewModel.increment() }) {
Text(text = "Increment")
}
}
}
在上述示例中,我们在 MyComposable
函数中使用 viewModel
函数获取 MyViewModel
的实例。然后,我们观察 viewModel.count
的变化,并在 UI 中显示计数器的值。点击按钮时,调用 viewModel.increment
方法增加计数器的值。
在某些情况下,我们可能需要使用自定义的 ViewModel 工厂来创建 ViewModel 实例。可以通过 viewModel
函数的 factory
参数来指定 ViewModel 工厂。
下面是一个示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
// 定义一个带有参数的 ViewModel 类
class MyViewModel(private val initialCount: Int) : ViewModel() {
private val _count = MutableLiveData(initialCount)
val count: LiveData<Int> = _count
fun increment() {
_count.value = _count.value?.plus(1)
}
}
// 定义一个自定义的 ViewModel 工厂
class MyViewModelFactory(private val initialCount: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MyViewModel(initialCount) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
@Composable
fun MyComposableWithFactory() {
val initialCount = 5
// 使用自定义的 ViewModel 工厂创建 ViewModel 实例
val viewModel: MyViewModel = viewModel(factory = MyViewModelFactory(initialCount))
Column {
viewModel.count.observeAsState().value?.let { count ->
Text(text = "Count: $count")
}
Button(onClick = { viewModel.increment() }) {
Text(text = "Increment")
}
}
}
在上述示例中,我们定义了一个带有参数的 MyViewModel
类,并创建了一个自定义的 MyViewModelFactory
来创建 MyViewModel
实例。在 MyComposableWithFactory
函数中,我们通过 viewModel
函数的 factory
参数指定了自定义的 ViewModel 工厂,从而创建了带有初始值的 MyViewModel
实例。
在 Android Compose 中,我们可以在不同的作用域中获取 ViewModel 实例。例如,我们可以在 Activity 或 Fragment 的作用域中获取 ViewModel 实例,也可以在特定的 Composable 函数的作用域中获取 ViewModel 实例。
下面是一个在 Activity 作用域中获取 ViewModel 实例的示例:
kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.setContent
import androidx.lifecycle.viewmodel.compose.viewModel
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 在 Activity 作用域中获取 ViewModel 实例
val viewModel: MyViewModel = viewModel()
Column {
viewModel.count.observeAsState().value?.let { count ->
Text(text = "Count: $count")
}
Button(onClick = { viewModel.increment() }) {
Text(text = "Increment")
}
}
}
}
}
在上述示例中,我们在 MainActivity
的 setContent
方法中使用 viewModel
函数获取 MyViewModel
实例,这样 MyViewModel
的生命周期将与 MainActivity
绑定。
viewModel
函数的源码解析viewModel
函数的定义如下:
kotlin
@Composable
inline fun <reified VM : ViewModel> viewModel(
key: String? = null,
factory: ViewModelProvider.Factory? = null,
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}
): VM {
val store = viewModelStoreOwner.viewModelStore
return ViewModelProvider(
store,
factory ?: defaultViewModelProviderFactory(viewModelStoreOwner)
).get(VM::class.java)
}
下面对 viewModel
函数的参数和实现进行详细解析:
参数说明:
key
:用于标识 ViewModel 实例的键,默认为 null
。如果需要在同一个 ViewModelStoreOwner
中创建多个相同类型的 ViewModel 实例,可以使用不同的键来区分它们。factory
:用于创建 ViewModel 实例的工厂,默认为 null
。如果不指定工厂,将使用默认的 ViewModel 工厂。viewModelStoreOwner
:ViewModelStoreOwner
对象,用于存储 ViewModel 实例。默认为 LocalViewModelStoreOwner.current
,即当前的 ViewModelStoreOwner
。实现细节:
viewModelStoreOwner
中获取 ViewModelStore
对象,ViewModelStore
用于存储和管理 ViewModel 实例。ViewModelProvider
对象,该对象用于创建和获取 ViewModel 实例。如果没有指定 factory
,将使用 defaultViewModelProviderFactory
函数获取默认的 ViewModel 工厂。ViewModelProvider
的 get
方法,根据指定的 ViewModel 类型获取 ViewModel 实例。defaultViewModelProviderFactory
函数的源码解析defaultViewModelProviderFactory
函数的定义如下:
kotlin
private fun defaultViewModelProviderFactory(owner: ViewModelStoreOwner): ViewModelProvider.Factory {
return if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelProviderFactory
} else {
NewInstanceFactory()
}
}
该函数用于获取默认的 ViewModel 工厂。如果 owner
实现了 HasDefaultViewModelProviderFactory
接口,则使用 owner
的 defaultViewModelProviderFactory
;否则,使用 NewInstanceFactory
作为默认的 ViewModel 工厂。NewInstanceFactory
是一个简单的 ViewModel 工厂,它通过反射创建 ViewModel 实例。
ViewModelProvider
类的源码分析ViewModelProvider
类用于创建和获取 ViewModel 实例,其主要方法和属性如下:
kotlin
class ViewModelProvider(
private val store: ViewModelStore,
private val factory: Factory
) {
// 定义一个接口,用于创建 ViewModel 实例
interface Factory {
fun <T : ViewModel> create(modelClass: Class<T>): T
}
// 获取指定类型的 ViewModel 实例
@MainThread
fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
require(canonicalName != null) { "Local and anonymous classes can not be ViewModels" }
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
// 根据键和类型获取 ViewModel 实例
@MainThread
fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
var viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
@Suppress("UNCHECKED_CAST")
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
viewModel = factory.create(modelClass)
store.put(key, viewModel)
return viewModel
}
}
Factory
接口:定义了一个 create
方法,用于创建 ViewModel 实例。不同的 ViewModel 工厂需要实现该接口。get
方法:有两个重载的 get
方法,一个根据 ViewModel 类型获取实例,另一个根据键和类型获取实例。在获取实例时,首先从 ViewModelStore
中查找是否已经存在该 ViewModel 实例,如果存在则直接返回;如果不存在,则使用 Factory
创建一个新的 ViewModel 实例,并将其存储在 ViewModelStore
中。ViewModelStore
类的源码分析ViewModelStore
类用于存储和管理 ViewModel 实例,其主要方法和属性如下:
kotlin
class ViewModelStore {
private val mMap = HashMap<String, ViewModel>()
// 根据键获取 ViewModel 实例
internal fun <T : ViewModel> get(key: String): T? {
@Suppress("UNCHECKED_CAST")
return mMap[key] as T?
}
// 根据键存储 ViewModel 实例
internal fun put(key: String, viewModel: ViewModel) {
val oldViewModel = mMap.put(key, viewModel)
oldViewModel?.onCleared()
}
// 清除所有的 ViewModel 实例
fun clear() {
for (vm in mMap.values) {
vm.onCleared()
}
mMap.clear()
}
}
mMap
:一个 HashMap
,用于存储 ViewModel 实例,键为 ViewModel 的标识,值为 ViewModel 实例。get
方法:根据键从 mMap
中获取 ViewModel 实例。put
方法:根据键将 ViewModel 实例存储在 mMap
中。如果该键已经存在对应的 ViewModel 实例,则调用其 onCleared
方法进行清理。clear
方法:清除 mMap
中所有的 ViewModel 实例,并调用每个实例的 onCleared
方法进行清理。在 Android 开发中,当设备的配置发生更改(如屏幕旋转)时,Activity 或 Fragment 会被重新创建,这可能导致数据的丢失。使用 ViewModel 委托可以很方便地实现数据的持久化和恢复。
下面是一个示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
// 定义一个 ViewModel 类,用于存储和管理数据
class DataViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun setData(newData: String) {
_data.value = newData
}
}
@Composable
fun DataPersistenceExample() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: DataViewModel = viewModel()
Column {
viewModel.data.observeAsState().value?.let { data ->
Text(text = "Data: $data")
}
Button(onClick = { viewModel.setData("New Data") }) {
Text(text = "Set Data")
}
}
}
在上述示例中,我们定义了一个 DataViewModel
类,用于存储和管理数据。在 DataPersistenceExample
函数中,使用 viewModel
委托获取 DataViewModel
实例。当点击按钮时,调用 viewModel.setData
方法设置新的数据。由于 ViewModel 的生命周期与 Activity 或 Fragment 分离,即使设备的配置发生更改,数据也不会丢失。
在 Android 应用中,可能需要在多个屏幕之间共享数据。使用 ViewModel 委托可以方便地实现这一功能。
下面是一个示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
// 定义一个 ViewModel 类,用于存储和管理共享数据
class SharedViewModel : ViewModel() {
private val _sharedData = MutableLiveData<String>()
val sharedData: LiveData<String> = _sharedData
fun setSharedData(newData: String) {
_sharedData.value = newData
}
}
@Composable
fun Screen1() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: SharedViewModel = viewModel()
Column {
Button(onClick = { viewModel.setSharedData("Data from Screen 1") }) {
Text(text = "Set Data from Screen 1")
}
}
}
@Composable
fun Screen2() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: SharedViewModel = viewModel()
Column {
viewModel.sharedData.observeAsState().value?.let { data ->
Text(text = "Shared Data: $data")
}
}
}
在上述示例中,我们定义了一个 SharedViewModel
类,用于存储和管理共享数据。在 Screen1
函数中,点击按钮时调用 viewModel.setSharedData
方法设置共享数据。在 Screen2
函数中,观察 viewModel.sharedData
的变化,并在 UI 中显示共享数据。由于 Screen1
和 Screen2
使用的是同一个 SharedViewModel
实例,因此可以实现数据的共享。
在 Android 应用中,可能会有一些复杂的业务逻辑需要处理。使用 ViewModel 委托可以将这些业务逻辑从 UI 层分离出来,提高代码的可维护性和可测试性。
下面是一个示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// 定义一个 ViewModel 类,用于处理复杂的业务逻辑
class ComplexViewModel : ViewModel() {
private val _result = MutableLiveData<String>()
val result: LiveData<String> = _result
fun performComplexTask() {
GlobalScope.launch(Dispatchers.IO) {
// 模拟复杂的业务逻辑
delay(2000)
_result.postValue("Task completed")
}
}
}
@Composable
fun ComplexLogicExample() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: ComplexViewModel = viewModel()
Column {
viewModel.result.observeAsState().value?.let { result ->
Text(text = "Result: $result")
}
Button(onClick = { viewModel.performComplexTask() }) {
Text(text = "Perform Complex Task")
}
}
}
在上述示例中,我们定义了一个 ComplexViewModel
类,其中的 performComplexTask
方法模拟了一个复杂的业务逻辑。在 ComplexLogicExample
函数中,使用 viewModel
委托获取 ComplexViewModel
实例。点击按钮时,调用 viewModel.performComplexTask
方法执行复杂的业务逻辑。当任务完成时,更新 _result
的值,并在 UI 中显示结果。
在使用 ViewModel 委托时,要避免不必要的 ViewModel 创建。如果在同一个作用域中多次调用 viewModel
函数获取同一个类型的 ViewModel 实例,应该确保使用相同的键和工厂,以避免创建多个相同类型的 ViewModel 实例。
kotlin
@Composable
fun MyComposable() {
// 正确的做法:使用相同的键和工厂获取 ViewModel 实例
val viewModel1: MyViewModel = viewModel()
val viewModel2: MyViewModel = viewModel()
// 错误的做法:可能会创建多个相同类型的 ViewModel 实例
// val viewModel3: MyViewModel = viewModel(key = "key1")
// val viewModel4: MyViewModel = viewModel(key = "key2")
}
在 ViewModel 中,如果使用了一些需要手动清理的资源(如网络连接、数据库连接等),应该在 onCleared
方法中进行清理。
kotlin
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
private var networkConnection: NetworkConnection? = null
init {
// 初始化网络连接
networkConnection = NetworkConnection()
}
override fun onCleared() {
super.onCleared()
// 清理网络连接
networkConnection?.close()
networkConnection = null
}
}
在使用 LiveData 时,要注意避免不必要的观察和数据更新。可以使用 observeAsState
方法将 LiveData 转换为 State
对象,这样可以在 Composable 函数中直接使用 LiveData 的值,并且只有当 LiveData 的值发生变化时才会重新组合。
kotlin
@Composable
fun MyComposable() {
val viewModel: MyViewModel = viewModel()
// 使用 observeAsState 方法将 LiveData 转换为 State 对象
val data by viewModel.data.observeAsState()
data?.let {
Text(text = "Data: $it")
}
}
No ViewModelStoreOwner was provided via LocalViewModelStoreOwner
错误这个错误通常是由于在没有提供 ViewModelStoreOwner
的情况下调用 viewModel
函数导致的。解决方案是确保在调用 viewModel
函数之前,已经通过 LocalViewModelStoreOwner
提供了 ViewModelStoreOwner
。
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
import androidx.lifecycle.ViewModel
import androidx.compose.ui.platform.LocalViewModelStoreOwner
@Composable
fun MyComposable() {
// 确保 LocalViewModelStoreOwner 不为空
val viewModelStoreOwner = LocalViewModelStoreOwner.current
if (viewModelStoreOwner != null) {
val viewModel: MyViewModel = viewModel(viewModelStoreOwner = viewModelStoreOwner)
Column {
viewModel.data.observeAsState().value?.let { data ->
Text(text = "Data: $data")
}
}
}
}
如果在不同的 Composable 函数中获取的 ViewModel 实例不共享,可能是由于使用了不同的 ViewModelStoreOwner
或不同的键和工厂。解决方案是确保在不同的 Composable 函数中使用相同的 ViewModelStoreOwner
、键和工厂。
kotlin
@Composable
fun Screen1() {
// 使用相同的 ViewModelStoreOwner、键和工厂获取 ViewModel 实例
val viewModel: SharedViewModel = viewModel()
// ...
}
@Composable
fun Screen2() {
// 使用相同的 ViewModelStoreOwner、键和工厂获取 ViewModel 实例
val viewModel: SharedViewModel = viewModel()
// ...
}
如果在 ViewModel 中持有了 Activity 或 Fragment 的引用,可能会导致内存泄漏。解决方案是避免在 ViewModel 中持有 Activity 或 Fragment 的引用,而是使用 Application
上下文或其他弱引用。
kotlin
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import android.app.Application
class MyViewModel(application: Application) : AndroidViewModel(application) {
// 使用 Application 上下文
private val context = application.applicationContext
// ...
}
ViewModel 委托可以与 Room 数据库集成,用于管理数据库操作和数据的展示。下面是一个简单的示例:
kotlin
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.lifecycle.ViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import android.content.Context
import androidx.room.Room
// 定义实体类
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String
)
// 定义 DAO 接口
@Dao
interface UserDao {
@Insert
fun insertUser(user: User)
@Query("SELECT * FROM users")
fun getAllUsers(): LiveData<List<User>>
}
// 定义数据库类
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// 定义 ViewModel 类
class UserViewModel(context: Context) : ViewModel() {
private val database = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"user-database"
).build()
private val userDao = database.userDao()
val allUsers: LiveData<List<User>> = userDao.getAllUsers()
fun insertUser(user: User) {
userDao.insertUser(user)
}
}
@Composable
fun UserListScreen(context: Context) {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: UserViewModel = viewModel(factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return UserViewModel(context) as T
}
})
Column {
viewModel.allUsers.observeAsState().value?.let { users ->
users.forEach { user ->
Text(text = "User: ${user.name}")
}
}
Button(onClick = {
val newUser = User(name = "New User")
viewModel.insertUser(newUser)
}) {
Text(text = "Add User")
}
}
}
在上述示例中,我们定义了一个 User
实体类、一个 UserDao
接口和一个 AppDatabase
类,用于管理数据库操作。在 UserViewModel
类中,我们使用 Room 数据库来存储和获取用户数据。在 UserListScreen
函数中,使用 viewModel
委托获取 UserViewModel
实例,并在 UI 中显示用户列表。点击按钮时,调用 viewModel.insertUser
方法添加新用户。
ViewModel 委托可以与 Retrofit 网络请求库集成,用于管理网络请求和数据的展示。下面是一个简单的示例:
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
// 定义数据模型类
data class Post(
val userId: Int,
val id: Int,
val title: String,
val body: String
)
// 定义 API 接口
interface PostApi {
@GET("posts")
suspend fun getPosts(): List<Post>
}
// 定义 ViewModel 类
class PostViewModel : ViewModel() {
private val _posts = MutableLiveData<List<Post>>()
val posts: LiveData<List<Post>> = _posts
init {
fetchPosts()
}
private fun fetchPosts() {
CoroutineScope(Dispatchers.IO).launch {
try {
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(PostApi::class.java)
val response = api.getPosts()
_posts.postValue(response)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
@Composable
fun PostListScreen() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: PostViewModel = viewModel()
Column {
viewModel.posts.observeAsState().value?.let { posts ->
posts.forEach { post ->
Text(text = "Title: ${post.title}")
}
}
Button(onClick = { viewModel.fetchPosts() }) {
Text(text = "Refresh Posts")
}
}
}
在上述示例中,我们定义了一个 Post
数据模型类和一个 PostApi
接口,用于定义网络请求。在 PostViewModel
类中,我们使用 Retrofit 发起网络请求,并将请求结果存储在 _posts
中。在 PostListScreen
函数中,使用 viewModel
委托获取 PostViewModel
实例,并在 UI 中显示帖子列表。点击按钮时,调用 viewModel.fetchPosts
方法刷新帖子列表。
ViewModel 委托可以与 Navigation 组件集成,用于在不同的屏幕之间共享数据和处理导航逻辑。下面是一个简单的示例:
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
// 定义 ViewModel 类
class NavigationViewModel : ViewModel() {
private val _selectedItem = MutableLiveData<String>()
val selectedItem: LiveData<String> = _selectedItem
fun setSelectedItem(item: String) {
_selectedItem.value = item
}
}
@Composable
fun NavigationExample() {
val navController = rememberNavController()
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: NavigationViewModel = viewModel()
NavHost(navController = navController, startDestination = "screen1") {
composable("screen1") {
Column {
Text(text = "Screen 1")
Button(onClick = {
viewModel.setSelectedItem("Item from Screen 1")
navController.navigate("screen2")
}) {
Text(text = "Go to Screen 2")
}
}
}
composable("screen2") {
Column {
viewModel.selectedItem.observeAsState().value?.let { item ->
Text(text = "Selected Item: $item")
}
Button(onClick = { navController.navigate("screen1") }) {
Text(text = "Go back to Screen 1")
}
}
}
}
}
在上述示例中,我们定义了一个 NavigationViewModel
类,用于存储和管理选中的项目。在 NavigationExample
函数中,使用 viewModel
委托获取 NavigationViewModel
实例。在 screen1
中,点击按钮时设置选中的项目并导航到 screen2
。在 screen2
中,显示选中的项目并提供返回 screen1
的按钮。
在一些复杂的 Android 应用中,可能存在多个 ViewModel,并非所有的 ViewModel 在应用启动时都需要立即初始化。为了优化性能和节省资源,可以实现 ViewModel 的懒加载,即只有在真正需要使用某个 ViewModel 时才进行初始化。
我们可以通过自定义委托来实现 ViewModel 的懒加载。委托可以在第一次访问 ViewModel 时进行初始化,后续再访问时直接返回已经初始化好的实例。
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import kotlin.reflect.KProperty
// 自定义委托类,用于实现 ViewModel 的懒加载
class LazyViewModelDelegate<VM : ViewModel>(
private val viewModelClass: Class<VM>,
private val factory: ViewModelProvider.Factory? = null
) {
private var viewModel: VM? = null
@Composable
operator fun getValue(thisRef: Any?, property: KProperty<*>): VM {
val viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw IllegalStateException("No ViewModelStoreOwner was provided via LocalViewModelStoreOwner")
return viewModel ?: run {
val vm = ViewModelProvider(
viewModelStoreOwner.viewModelStore,
factory ?: defaultViewModelProviderFactory(viewModelStoreOwner)
).get(viewModelClass)
viewModel = vm
vm
}
}
private fun defaultViewModelProviderFactory(owner: ViewModelStoreOwner): ViewModelProvider.Factory {
return if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelProviderFactory
} else {
NewInstanceFactory()
}
}
}
// 定义一个扩展函数,方便使用懒加载委托
@Composable
inline fun <reified VM : ViewModel> lazyViewModel(
factory: ViewModelProvider.Factory? = null
): LazyViewModelDelegate<VM> {
return LazyViewModelDelegate(VM::class.java, factory)
}
// 示例 ViewModel 类
class LazyViewModel : ViewModel() {
init {
println("LazyViewModel initialized")
}
}
@Composable
fun LazyViewModelExample() {
// 使用懒加载委托获取 ViewModel 实例
val lazyViewModel: LazyViewModel by lazyViewModel()
// 模拟只有在满足某个条件时才使用 ViewModel
if (true) {
// 第一次访问时才会初始化 ViewModel
println("Using LazyViewModel: ${lazyViewModel.hashCode()}")
}
}
viewModel
变量,用于存储已经初始化的 ViewModel 实例。在 getValue
方法中,首先检查 viewModel
是否已经初始化,如果没有,则使用 ViewModelProvider
进行初始化,并将其赋值给 viewModel
变量。LazyViewModelDelegate
实例,方便在 Composable 函数中使用。lazyViewModel
扩展函数获取 LazyViewModel
实例。只有在满足某个条件时才会访问 LazyViewModel
,从而触发其初始化。在某些场景下,可能需要根据用户的操作或应用的状态动态地创建和销毁 ViewModel。例如,在一个多步骤的表单填写界面中,每个步骤可能需要一个独立的 ViewModel 来管理数据,当用户完成某个步骤后,需要销毁该步骤对应的 ViewModel 以释放资源。
可以通过在 ViewModelStore
中手动管理 ViewModel 的创建和销毁来实现动态创建和销毁。在需要创建 ViewModel 时,使用 ViewModelProvider
创建实例并存储在 ViewModelStore
中;在需要销毁 ViewModel 时,从 ViewModelStore
中移除该实例并调用其 onCleared
方法。
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
// 示例 ViewModel 类
class DynamicViewModel : ViewModel() {
init {
println("DynamicViewModel initialized")
}
override fun onCleared() {
super.onCleared()
println("DynamicViewModel cleared")
}
}
@Composable
fun DynamicViewModelExample() {
val viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw IllegalStateException("No ViewModelStoreOwner was provided via LocalViewModelStoreOwner")
val viewModelStore = viewModelStoreOwner.viewModelStore
val viewModelFactory = defaultViewModelProviderFactory(viewModelStoreOwner)
var dynamicViewModel: DynamicViewModel? = null
Column {
Button(onClick = {
// 动态创建 ViewModel
dynamicViewModel = ViewModelProvider(viewModelStore, viewModelFactory)
.get(DynamicViewModel::class.java)
println("DynamicViewModel created: ${dynamicViewModel?.hashCode()}")
}) {
Text(text = "Create DynamicViewModel")
}
Button(onClick = {
// 动态销毁 ViewModel
dynamicViewModel?.let {
viewModelStore.clear()
dynamicViewModel = null
}
}) {
Text(text = "Destroy DynamicViewModel")
}
}
}
private fun defaultViewModelProviderFactory(owner: ViewModelStoreOwner): ViewModelProvider.Factory {
return if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelProviderFactory
} else {
NewInstanceFactory()
}
}
onCleared
方法中打印一条日志表示销毁。DynamicViewModel
。在创建按钮的点击事件中,使用 ViewModelProvider
创建 DynamicViewModel
实例并存储在 dynamicViewModel
变量中;在销毁按钮的点击事件中,调用 viewModelStore.clear()
方法销毁 ViewModelStore
中的所有 ViewModel 实例,并将 dynamicViewModel
变量置为 null
。在某些情况下,可能需要在同一个 ViewModelStoreOwner
中创建多个相同类型的 ViewModel 实例。例如,在一个列表界面中,每个列表项可能需要一个独立的 ViewModel 来管理其数据。
可以通过为每个 ViewModel 实例指定不同的键来实现多实例管理。在使用 ViewModelProvider
获取 ViewModel 实例时,传入不同的键,这样 ViewModelStore
就会将这些实例视为不同的对象进行存储和管理。
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
// 示例 ViewModel 类
class MultiInstanceViewModel : ViewModel() {
init {
println("MultiInstanceViewModel initialized")
}
}
@Composable
fun MultiInstanceViewModelExample() {
val viewModelStoreOwner = LocalViewModelStoreOwner.current
?: throw IllegalStateException("No ViewModelStoreOwner was provided via LocalViewModelStoreOwner")
val viewModelStore = viewModelStoreOwner.viewModelStore
val viewModelFactory = defaultViewModelProviderFactory(viewModelStoreOwner)
// 创建多个 ViewModel 实例
val viewModel1: MultiInstanceViewModel = ViewModelProvider(viewModelStore, viewModelFactory)
.get("key1", MultiInstanceViewModel::class.java)
val viewModel2: MultiInstanceViewModel = ViewModelProvider(viewModelStore, viewModelFactory)
.get("key2", MultiInstanceViewModel::class.java)
Column {
Text(text = "ViewModel 1: ${viewModel1.hashCode()}")
Text(text = "ViewModel 2: ${viewModel2.hashCode()}")
}
}
private fun defaultViewModelProviderFactory(owner: ViewModelStoreOwner): ViewModelProvider.Factory {
return if (owner is HasDefaultViewModelProviderFactory) {
owner.defaultViewModelProviderFactory
} else {
NewInstanceFactory()
}
}
ViewModelProvider
的 get
方法,分别传入不同的键("key1"
和 "key2"
)来创建两个 MultiInstanceViewModel
实例。最后在 UI 中显示这两个实例的哈希码,以验证它们是不同的对象。在某些情况下,可能需要在应用的生命周期中保存和恢复 ViewModel 的状态。例如,当应用进入后台或发生配置更改时,需要保存 ViewModel 的数据,以便在应用恢复时能够恢复到之前的状态。
可以通过 SavedStateHandle
来实现 ViewModel 的状态保存和恢复。SavedStateHandle
是一个键值对存储,用于在 ViewModel 的生命周期中保存和恢复数据。在 ViewModel 的构造函数中注入 SavedStateHandle
,并在需要保存状态时将数据存储在 SavedStateHandle
中,在需要恢复状态时从 SavedStateHandle
中读取数据。
kotlin
import androidx.compose.runtime.Composable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
// 示例 ViewModel 类,使用 SavedStateHandle 保存和恢复状态
class StatefulViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
private val KEY_COUNT = "count"
var count: Int
get() = savedStateHandle.get<Int>(KEY_COUNT) ?: 0
set(value) {
savedStateHandle[KEY_COUNT] = value
}
init {
println("StatefulViewModel initialized")
}
}
@Composable
fun StatefulViewModelExample() {
// 使用 viewModel 委托获取 ViewModel 实例
val viewModel: StatefulViewModel = viewModel()
Column {
Text(text = "Count: ${viewModel.count}")
Button(onClick = {
viewModel.count++
}) {
Text(text = "Increment Count")
}
}
}
SavedStateHandle
。通过 count
属性来访问和修改存储在 SavedStateHandle
中的计数器值。在 get
方法中,从 SavedStateHandle
中读取计数器值,如果不存在则返回 0;在 set
方法中,将新的计数器值存储在 SavedStateHandle
中。viewModel
委托获取 StatefulViewModel
实例。在 UI 中显示计数器的值,并提供一个按钮用于增加计数器的值。当应用发生配置更改或进入后台时,SavedStateHandle
会自动保存和恢复计数器的值。在进行单元测试时,首先需要测试 ViewModel 的初始化是否正确。可以使用 JUnit 和 Mockito 等测试框架来编写测试用例。
kotlin
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@RunWith(AndroidJUnit4::class)
class StatefulViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var savedStateHandle: SavedStateHandle
private lateinit var viewModel: StatefulViewModel
@Before
fun setup() {
savedStateHandle = Mockito.mock(SavedStateHandle::class.java)
viewModel = StatefulViewModel(savedStateHandle)
}
@Test
fun testViewModelInitialization() {
// 验证 ViewModel 初始化时是否正确处理 SavedStateHandle
// 这里可以根据具体的逻辑添加更多的验证代码
}
}
SavedStateHandle
的模拟对象,并使用它来初始化 StatefulViewModel
。除了测试 ViewModel 的初始化,还需要测试 ViewModel 的业务逻辑。例如,测试 ViewModel 中的方法是否正确处理数据和更新 LiveData。
kotlin
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@RunWith(AndroidJUnit4::class)
class StatefulViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var savedStateHandle: SavedStateHandle
private lateinit var viewModel: StatefulViewModel
@Before
fun setup() {
savedStateHandle = Mockito.mock(SavedStateHandle::class.java)
viewModel = StatefulViewModel(savedStateHandle)
}
@Test
fun testIncrementCount() {
// 模拟 SavedStateHandle 的行为
Mockito.`when`(savedStateHandle.get<Int>("count")).thenReturn(0)
// 调用 ViewModel 的方法
viewModel.count++
// 验证 SavedStateHandle 是否被正确更新
Mockito.verify(savedStateHandle).set("count", 1)
}
}
StatefulViewModel
中的 count
属性的递增逻辑。首先,使用 Mockito 模拟 SavedStateHandle
的行为,设置初始计数器值为 0。然后,调用 viewModel.count++
方法增加计数器的值。最后,使用 Mockito 的 verify
方法验证 SavedStateHandle
是否被正确更新。还需要测试 ViewModel 的状态保存和恢复功能。可以通过模拟配置更改或应用进入后台的场景,验证 ViewModel 的状态是否能够正确保存和恢复。
kotlin
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@RunWith(AndroidJUnit4::class)
class StatefulViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var savedStateHandle: SavedStateHandle
private lateinit var viewModel: StatefulViewModel
@Before
fun setup() {
savedStateHandle = Mockito.mock(SavedStateHandle::class.java)
viewModel = StatefulViewModel(savedStateHandle)
}
@Test
fun testStateSaveAndRestore() {
// 设置初始状态
viewModel.count = 5
// 模拟状态保存
Mockito.`when`(savedStateHandle.get<Int>("count")).thenReturn(5)
// 重新创建 ViewModel
val newViewModel = StatefulViewModel(savedStateHandle)
// 验证状态是否正确恢复
assert(newViewModel.count == 5)
}
}
StatefulViewModel
的状态保存和恢复功能。首先,设置初始计数器值为 5。然后,使用 Mockito 模拟 SavedStateHandle
的行为,设置保存的计数器值为 5。接着,重新创建 StatefulViewModel
实例。最后,验证新的 ViewModel 实例的计数器值是否正确恢复为 5。在 Android Compose 框架中,ViewModel 委托为开发者提供了一种简洁、高效的方式来管理和使用 ViewModel。通过使用 viewModel
函数,我们可以在 Composable 函数中方便地获取 ViewModel 实例,避免了手动创建和管理 ViewModel 的繁琐过程。
ViewModel 委托具有以下优点:
简化代码:减少了样板代码,使代码更加简洁易读。
自动生命周期管理:ViewModel 的生命周期与 ViewModelStoreOwner
绑定,确保在配置更改时数据不会丢失,并且在不再需要时自动销毁。
提高可测试性:将 ViewModel 的创建和管理与 Composable 函数分离,使得 Composable 函数更加易于测试。
同时,我们还深入分析了 ViewModel 委托的源码,了解了其工作原理和实现细节。通过源码分析,我们可以更好地理解 ViewModel 委托的使用方式,以及如何进行性能优化和处理常见问题。
在实际应用中,ViewModel 委托可以用于数据的持久化和恢复、多屏幕之间的数据共享、处理复杂的业务逻辑等场景。此外,我们还介绍了 ViewModel 委托的高级应用,如懒加载、动态创建和销毁、多实例管理以及状态保存和恢复等,这些高级应用可以帮助我们更好地应对复杂的业务需求。
随着 Android 开发技术的不断发展,ViewModel 委托可能会有以下几个方面的发展和改进:
未来可能会为 ViewModel 委托添加更多的功能扩展,例如支持更多的 ViewModel 工厂类型、提供更灵活的状态保存和恢复机制等。这将使得开发者能够更加方便地处理各种复杂的业务场景。
随着 Android 设备性能的不断提升,对应用性能的要求也越来越高。未来的 ViewModel 委托可能会在性能优化方面进行更多的改进,例如减少内存占用、提高初始化和销毁的效率等。
ViewModel 委托可能会与其他 Android 架构组件进行更深入的集成,例如与 Compose Navigation、Hilt 等组件的集成,提供更加无缝的开发体验。
随着跨平台开发的需求不断增加,ViewModel 委托可能会支持跨平台开发,例如在 Kotlin Multiplatform Mobile(KMM)项目中使用,使得开发者能够在不同平台上共享 ViewModel 的逻辑。
总之,ViewModel 委托作为 Android Compose 框架中的重要组成部分,将在未来的 Android 开发中发挥更加重要的作用。开发者可以充分利用 ViewModel 委托的优势,提高开发效率,构建更加高质量的 Android 应用。