当前位置: 首页 > news >正文

Android Compose 框架的 ViewModel 委托深入剖析(二十)

Android Compose 框架的 ViewModel 委托深入剖析

一、引言

在 Android 开发中,数据的管理和状态的保存是至关重要的。ViewModel 作为 Android 架构组件的一部分,为我们提供了一种在配置更改(如屏幕旋转)时保存数据和管理 UI 状态的有效方式。而在 Android Compose 中,ViewModel 委托进一步简化了 ViewModel 的使用,使得开发者能够更加方便地在 Composable 函数中获取和使用 ViewModel。

本文将深入分析 Android Compose 框架中的 ViewModel 委托,从基础概念入手,逐步深入到源码级别,详细探讨其工作原理、使用方法、实际应用场景以及性能优化等方面。通过本文的学习,你将对 ViewModel 委托有一个全面而深入的理解,能够在实际开发中更加熟练地运用它来构建高质量的 Android 应用。

二、ViewModel 基础概念回顾

2.1 ViewModel 的定义和作用

ViewModel 是 Android 架构组件中的一个类,用于存储和管理与 UI 相关的数据,并且在配置更改(如屏幕旋转)时保持数据的一致性。它的主要作用包括:

  • 分离 UI 逻辑和数据逻辑:ViewModel 可以将与 UI 相关的数据和业务逻辑从 Activity 或 Fragment 中分离出来,使得 UI 层只负责展示数据,而数据的管理和处理则由 ViewModel 负责。
  • 配置更改时数据保持:当设备的配置发生更改(如屏幕旋转)时,Activity 或 Fragment 会被重新创建,但 ViewModel 会保持不变,从而避免了数据的丢失。
  • 数据共享:ViewModel 可以在多个 Fragment 或 Activity 之间共享数据,方便实现不同界面之间的数据交互。

2.2 ViewModel 的基本使用示例

下面是一个简单的 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.ActivityMainBindingclass MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)// 使用 ViewModelProvider 获取 ViewModel 实例viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// 观察 LiveData 的变化,并更新 UIviewModel.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 方法增加计数器的值。

三、ViewModel 委托的基本概念

3.1 委托的概念

委托是 Kotlin 语言中的一个特性,它允许将一个对象的某些操作委托给另一个对象来处理。在 Kotlin 中,委托可以分为类委托和属性委托。类委托允许一个类将其部分或全部方法的实现委托给另一个对象,而属性委托则允许将属性的 getter 和 setter 方法委托给另一个对象。

3.2 ViewModel 委托的定义和作用

ViewModel 委托是一种在 Android Compose 中使用的属性委托,它允许我们在 Composable 函数中方便地获取和使用 ViewModel。通过 ViewModel 委托,我们可以避免在 Composable 函数中手动创建和管理 ViewModel 实例,从而简化代码,提高开发效率。

3.3 ViewModel 委托的优势

  • 简化代码:使用 ViewModel 委托可以避免在 Composable 函数中手动创建和管理 ViewModel 实例,减少了样板代码,使代码更加简洁。
  • 自动生命周期管理:ViewModel 委托会自动处理 ViewModel 的生命周期,确保 ViewModel 在配置更改时保持不变,并且在不再需要时被正确销毁。
  • 提高可测试性:由于 ViewModel 委托将 ViewModel 的创建和管理与 Composable 函数分离,使得 Composable 函数更加易于测试。

四、ViewModel 委托的基本使用

4.1 使用 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> = _countfun increment() {_count.value = _count.value?.plus(1)}
}@Composable
fun MyComposable() {// 使用 viewModel 函数获取 ViewModel 实例val viewModel: MyViewModel = viewModel()Column {// 观察 LiveData 的变化,并更新 UIviewModel.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 方法增加计数器的值。

4.2 指定 ViewModel 的工厂

在某些情况下,我们可能需要使用自定义的 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> = _countfun 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 实例。

4.3 在不同的作用域中获取 ViewModel 实例

在 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.viewModelclass 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 委托的源码分析

5.1 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.viewModelStorereturn ViewModelProvider(store,factory ?: defaultViewModelProviderFactory(viewModelStoreOwner)).get(VM::class.java)
}

下面对 viewModel 函数的参数和实现进行详细解析:

  • 参数说明

    • key:用于标识 ViewModel 实例的键,默认为 null。如果需要在同一个 ViewModelStoreOwner 中创建多个相同类型的 ViewModel 实例,可以使用不同的键来区分它们。
    • factory:用于创建 ViewModel 实例的工厂,默认为 null。如果不指定工厂,将使用默认的 ViewModel 工厂。
    • viewModelStoreOwnerViewModelStoreOwner 对象,用于存储 ViewModel 实例。默认为 LocalViewModelStoreOwner.current,即当前的 ViewModelStoreOwner
  • 实现细节

    1. 首先,从 viewModelStoreOwner 中获取 ViewModelStore 对象,ViewModelStore 用于存储和管理 ViewModel 实例。
    2. 然后,创建一个 ViewModelProvider 对象,该对象用于创建和获取 ViewModel 实例。如果没有指定 factory,将使用 defaultViewModelProviderFactory 函数获取默认的 ViewModel 工厂。
    3. 最后,调用 ViewModelProvider 的 get 方法,根据指定的 ViewModel 类型获取 ViewModel 实例。

5.2 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 实例。

5.3 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 实例@MainThreadfun <T : ViewModel> get(modelClass: Class<T>): T {val canonicalName = modelClass.canonicalNamerequire(canonicalName != null) { "Local and anonymous classes can not be ViewModels" }return get("$DEFAULT_KEY:$canonicalName", modelClass)}// 根据键和类型获取 ViewModel 实例@MainThreadfun <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 中。

5.4 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 方法进行清理。

六、ViewModel 委托的实际应用场景

6.1 数据的持久化和恢复

在 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> = _datafun 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 分离,即使设备的配置发生更改,数据也不会丢失。

6.2 多屏幕之间的数据共享

在 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> = _sharedDatafun 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 实例,因此可以实现数据的共享。

6.3 处理复杂的业务逻辑

在 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> = _resultfun 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 委托的性能优化

7.1 避免不必要的 ViewModel 创建

在使用 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")
}

7.2 及时清理 ViewModel 中的资源

在 ViewModel 中,如果使用了一些需要手动清理的资源(如网络连接、数据库连接等),应该在 onCleared 方法中进行清理。

kotlin

import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {private var networkConnection: NetworkConnection? = nullinit {// 初始化网络连接networkConnection = NetworkConnection()}override fun onCleared() {super.onCleared()// 清理网络连接networkConnection?.close()networkConnection = null}
}

7.3 优化 LiveData 的使用

在使用 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")}
}

八、ViewModel 委托的常见问题及解决方案

8.1 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.currentif (viewModelStoreOwner != null) {val viewModel: MyViewModel = viewModel(viewModelStoreOwner = viewModelStoreOwner)Column {viewModel.data.observeAsState().value?.let { data ->Text(text = "Data: $data")}}}
}

8.2 ViewModel 实例不共享的问题

如果在不同的 Composable 函数中获取的 ViewModel 实例不共享,可能是由于使用了不同的 ViewModelStoreOwner 或不同的键和工厂。解决方案是确保在不同的 Composable 函数中使用相同的 ViewModelStoreOwner、键和工厂。

kotlin

@Composable
fun Screen1() {// 使用相同的 ViewModelStoreOwner、键和工厂获取 ViewModel 实例val viewModel: SharedViewModel = viewModel()// ...
}@Composable
fun Screen2() {// 使用相同的 ViewModelStoreOwner、键和工厂获取 ViewModel 实例val viewModel: SharedViewModel = viewModel()// ...
}

8.3 ViewModel 内存泄漏的问题

如果在 ViewModel 中持有了 Activity 或 Fragment 的引用,可能会导致内存泄漏。解决方案是避免在 ViewModel 中持有 Activity 或 Fragment 的引用,而是使用 Application 上下文或其他弱引用。

kotlin

import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import android.app.Applicationclass MyViewModel(application: Application) : AndroidViewModel(application) {// 使用 Application 上下文private val context = application.applicationContext// ...
}

九、ViewModel 委托与其他 Android 架构组件的集成

9.1 与 Room 数据库的集成

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 {@Insertfun 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 方法添加新用户。

9.2 与 Retrofit 网络请求库的集成

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>> = _postsinit {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 方法刷新帖子列表。

9.3 与 Navigation 组件的集成

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> = _selectedItemfun 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 的按钮。

十、ViewModel 委托的高级应用

10.1 实现 ViewModel 的懒加载

在一些复杂的 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@Composableoperator 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 = vmvm}}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()// 模拟只有在满足某个条件时才使用 ViewModelif (true) {// 第一次访问时才会初始化 ViewModelprintln("Using LazyViewModel: ${lazyViewModel.hashCode()}")}
}
代码解释
  1. LazyViewModelDelegate 类:这是一个自定义的委托类,用于实现 ViewModel 的懒加载。它包含一个 viewModel 变量,用于存储已经初始化的 ViewModel 实例。在 getValue 方法中,首先检查 viewModel 是否已经初始化,如果没有,则使用 ViewModelProvider 进行初始化,并将其赋值给 viewModel 变量。
  2. lazyViewModel 扩展函数:这是一个扩展函数,用于创建 LazyViewModelDelegate 实例,方便在 Composable 函数中使用。
  3. LazyViewModel 类:这是一个示例 ViewModel 类,在其构造函数中打印一条日志,用于验证初始化时机。
  4. LazyViewModelExample 函数:这是一个 Composable 函数,使用 lazyViewModel 扩展函数获取 LazyViewModel 实例。只有在满足某个条件时才会访问 LazyViewModel,从而触发其初始化。

10.2 实现 ViewModel 的动态创建和销毁

在某些场景下,可能需要根据用户的操作或应用的状态动态地创建和销毁 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.viewModelStoreval viewModelFactory = defaultViewModelProviderFactory(viewModelStoreOwner)var dynamicViewModel: DynamicViewModel? = nullColumn {Button(onClick = {// 动态创建 ViewModeldynamicViewModel = ViewModelProvider(viewModelStore, viewModelFactory).get(DynamicViewModel::class.java)println("DynamicViewModel created: ${dynamicViewModel?.hashCode()}")}) {Text(text = "Create DynamicViewModel")}Button(onClick = {// 动态销毁 ViewModeldynamicViewModel?.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()}
}
代码解释
  1. DynamicViewModel 类:这是一个示例 ViewModel 类,在其构造函数中打印一条日志表示初始化,在 onCleared 方法中打印一条日志表示销毁。
  2. DynamicViewModelExample 函数:这是一个 Composable 函数,包含两个按钮,分别用于动态创建和销毁 DynamicViewModel。在创建按钮的点击事件中,使用 ViewModelProvider 创建 DynamicViewModel 实例并存储在 dynamicViewModel 变量中;在销毁按钮的点击事件中,调用 viewModelStore.clear() 方法销毁 ViewModelStore 中的所有 ViewModel 实例,并将 dynamicViewModel 变量置为 null

10.3 实现 ViewModel 的多实例管理

在某些情况下,可能需要在同一个 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.viewModelStoreval 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()}
}
代码解释
  1. MultiInstanceViewModel 类:这是一个示例 ViewModel 类,在其构造函数中打印一条日志表示初始化。
  2. MultiInstanceViewModelExample 函数:这是一个 Composable 函数,使用 ViewModelProvider 的 get 方法,分别传入不同的键("key1" 和 "key2")来创建两个 MultiInstanceViewModel 实例。最后在 UI 中显示这两个实例的哈希码,以验证它们是不同的对象。

10.4 实现 ViewModel 的状态保存和恢复

在某些情况下,可能需要在应用的生命周期中保存和恢复 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: Intget() = savedStateHandle.get<Int>(KEY_COUNT) ?: 0set(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")}}
}
代码解释
  1. StatefulViewModel 类:这是一个示例 ViewModel 类,在其构造函数中注入 SavedStateHandle。通过 count 属性来访问和修改存储在 SavedStateHandle 中的计数器值。在 get 方法中,从 SavedStateHandle 中读取计数器值,如果不存在则返回 0;在 set 方法中,将新的计数器值存储在 SavedStateHandle 中。
  2. StatefulViewModelExample 函数:这是一个 Composable 函数,使用 viewModel 委托获取 StatefulViewModel 实例。在 UI 中显示计数器的值,并提供一个按钮用于增加计数器的值。当应用发生配置更改或进入后台时,SavedStateHandle 会自动保存和恢复计数器的值。

十一、ViewModel 委托的单元测试

11.1 测试 ViewModel 的初始化

在进行单元测试时,首先需要测试 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:Ruleval instantTaskExecutorRule = InstantTaskExecutorRule()private lateinit var savedStateHandle: SavedStateHandleprivate lateinit var viewModel: StatefulViewModel@Beforefun setup() {savedStateHandle = Mockito.mock(SavedStateHandle::class.java)viewModel = StatefulViewModel(savedStateHandle)}@Testfun testViewModelInitialization() {// 验证 ViewModel 初始化时是否正确处理 SavedStateHandle// 这里可以根据具体的逻辑添加更多的验证代码}
}
代码解释
  1. InstantTaskExecutorRule:这是一个 JUnit 规则,用于在测试环境中同步执行 LiveData 的任务,确保测试的准确性。
  2. setup 方法:在每个测试用例执行之前,创建 SavedStateHandle 的模拟对象,并使用它来初始化 StatefulViewModel
  3. testViewModelInitialization 方法:这是一个测试用例,用于验证 ViewModel 的初始化是否正确。可以根据具体的逻辑添加更多的验证代码。

11.2 测试 ViewModel 的业务逻辑

除了测试 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:Ruleval instantTaskExecutorRule = InstantTaskExecutorRule()private lateinit var savedStateHandle: SavedStateHandleprivate lateinit var viewModel: StatefulViewModel@Beforefun setup() {savedStateHandle = Mockito.mock(SavedStateHandle::class.java)viewModel = StatefulViewModel(savedStateHandle)}@Testfun testIncrementCount() {// 模拟 SavedStateHandle 的行为Mockito.`when`(savedStateHandle.get<Int>("count")).thenReturn(0)// 调用 ViewModel 的方法viewModel.count++// 验证 SavedStateHandle 是否被正确更新Mockito.verify(savedStateHandle).set("count", 1)}
}
代码解释
  1. testIncrementCount 方法:这是一个测试用例,用于测试 StatefulViewModel 中的 count 属性的递增逻辑。首先,使用 Mockito 模拟 SavedStateHandle 的行为,设置初始计数器值为 0。然后,调用 viewModel.count++ 方法增加计数器的值。最后,使用 Mockito 的 verify 方法验证 SavedStateHandle 是否被正确更新。

11.3 测试 ViewModel 的状态保存和恢复

还需要测试 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:Ruleval instantTaskExecutorRule = InstantTaskExecutorRule()private lateinit var savedStateHandle: SavedStateHandleprivate lateinit var viewModel: StatefulViewModel@Beforefun setup() {savedStateHandle = Mockito.mock(SavedStateHandle::class.java)viewModel = StatefulViewModel(savedStateHandle)}@Testfun testStateSaveAndRestore() {// 设置初始状态viewModel.count = 5// 模拟状态保存Mockito.`when`(savedStateHandle.get<Int>("count")).thenReturn(5)// 重新创建 ViewModelval newViewModel = StatefulViewModel(savedStateHandle)// 验证状态是否正确恢复assert(newViewModel.count == 5)}
}
代码解释
  1. testStateSaveAndRestore 方法:这是一个测试用例,用于测试 StatefulViewModel 的状态保存和恢复功能。首先,设置初始计数器值为 5。然后,使用 Mockito 模拟 SavedStateHandle 的行为,设置保存的计数器值为 5。接着,重新创建 StatefulViewModel 实例。最后,验证新的 ViewModel 实例的计数器值是否正确恢复为 5。

十二、总结与展望

12.1 总结

在 Android Compose 框架中,ViewModel 委托为开发者提供了一种简洁、高效的方式来管理和使用 ViewModel。通过使用 viewModel 函数,我们可以在 Composable 函数中方便地获取 ViewModel 实例,避免了手动创建和管理 ViewModel 的繁琐过程。

ViewModel 委托具有以下优点:

  • 简化代码:减少了样板代码,使代码更加简洁易读。

  • 自动生命周期管理:ViewModel 的生命周期与 ViewModelStoreOwner 绑定,确保在配置更改时数据不会丢失,并且在不再需要时自动销毁。

  • 提高可测试性:将 ViewModel 的创建和管理与 Composable 函数分离,使得 Composable 函数更加易于测试。

同时,我们还深入分析了 ViewModel 委托的源码,了解了其工作原理和实现细节。通过源码分析,我们可以更好地理解 ViewModel 委托的使用方式,以及如何进行性能优化和处理常见问题。

在实际应用中,ViewModel 委托可以用于数据的持久化和恢复、多屏幕之间的数据共享、处理复杂的业务逻辑等场景。此外,我们还介绍了 ViewModel 委托的高级应用,如懒加载、动态创建和销毁、多实例管理以及状态保存和恢复等,这些高级应用可以帮助我们更好地应对复杂的业务需求。

12.2 展望

随着 Android 开发技术的不断发展,ViewModel 委托可能会有以下几个方面的发展和改进:

更强大的功能扩展

未来可能会为 ViewModel 委托添加更多的功能扩展,例如支持更多的 ViewModel 工厂类型、提供更灵活的状态保存和恢复机制等。这将使得开发者能够更加方便地处理各种复杂的业务场景。

更好的性能优化

随着 Android 设备性能的不断提升,对应用性能的要求也越来越高。未来的 ViewModel 委托可能会在性能优化方面进行更多的改进,例如减少内存占用、提高初始化和销毁的效率等。

与其他组件的深度集成

ViewModel 委托可能会与其他 Android 架构组件进行更深入的集成,例如与 Compose Navigation、Hilt 等组件的集成,提供更加无缝的开发体验。

跨平台支持

随着跨平台开发的需求不断增加,ViewModel 委托可能会支持跨平台开发,例如在 Kotlin Multiplatform Mobile(KMM)项目中使用,使得开发者能够在不同平台上共享 ViewModel 的逻辑。

总之,ViewModel 委托作为 Android Compose 框架中的重要组成部分,将在未来的 Android 开发中发挥更加重要的作用。开发者可以充分利用 ViewModel 委托的优势,提高开发效率,构建更加高质量的 Android 应用。

相关文章:

Android Compose 框架的 ViewModel 委托深入剖析(二十)

Android Compose 框架的 ViewModel 委托深入剖析 一、引言 在 Android 开发中&#xff0c;数据的管理和状态的保存是至关重要的。ViewModel 作为 Android 架构组件的一部分&#xff0c;为我们提供了一种在配置更改&#xff08;如屏幕旋转&#xff09;时保存数据和管理 UI 状态…...

android|生成二维码qrcode(android)

1.build.gradle implementation com.google.zxing:core:3.4.1引入zxing库 只是生成的话引入core库就可以了 2.封装方法 import android.graphics.Bitmap; import android.graphics.Color;import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; imp…...

element-plus中el-empty空盒子组件和Collapse 折叠面板组件的使用

一.el-empty空盒子组件的使用 直接复制下面的代码&#xff1a; <el-empty description"description" /> 展示效果&#xff1a; 还可以自定义文字描述&#xff1a; <el-empty description"暂未选择患者"/> 二.Collapse 折叠面板组件的使用 复制…...

Windows 和 Linux 操作系统架构对比以及交叉编译

操作系统与架构兼容性详解 1. 可执行文件格式&#xff1a;PE vs ELF Windows: PE (Portable Executable) 格式 详细解释&#xff1a; PE 格式是 Windows 下的可执行文件标准 包含多个区段&#xff08;Sections&#xff09;&#xff0c;如代码段、数据段、资源段 文件头包含…...

【区块链安全 | 第一篇】密码学原理

文章目录 1.哈希函数1.1 哈希函数的性质1.2 常见哈希算法1.3 Merkle Tree&#xff08;默克尔树&#xff09;1.4 HMAC&#xff08;哈希消息认证码&#xff09; 2. 公钥密码学2.1 对称加密 vs 非对称加密2.2 RSA 算法2.3 ECC&#xff08;椭圆曲线密码学&#xff09;2.4 Diffie-He…...

3.23[A]linux

gedit 是 GNOME 桌面环境下的文本编辑器&#xff0c;类似于 Windows 中的记事本&#xff0c;但功能更强大&#xff0c;支持语法高亮、多文件编辑等特性。它是一个图形化界面的文本编辑器&#xff0c;适合在需要直观编辑文本文件的场景中使用。 gedit 通常用于编辑配置文件、源代…...

AI革命之下的前端将会如何发展?

一、AI 为前端开发带来的变革 &#xff08;一&#xff09;提升开发效率 传统的 Web 前端开发常常面临大量重复性工作&#xff0c;如编写简单表单、布局组件等&#xff0c;这些工作耗时费力且易出错&#xff0c;严重影响开发效率和项目进度。而 AI 的出现&#xff0c;通过自动…...

【2025】基于springboot+vue的农产品商城系统设计与实现(源码、万字文档、图文修改、调试答疑)

项目完整功能以演示视频为准 基于Spring Boot Vue的农产品商城系统设计与实现功能结构图如下&#xff1a; 课题背景 随着互联网的普及和电子商务的快速发展&#xff0c;农产品线上销售成为推动农业现代化和乡村振兴的重要力量。传统的农产品销售模式存在信息不对称、销售渠道单…...

沪深300股指期货的看涨看跌方式是怎样的?

沪深300指数代表了中国A股市场中300家大公司的整体表现。股指期货交易允许老板们预测指数未来的涨跌&#xff0c;并从中获利。 沪深300股指期货基础操作 首先&#xff0c;沪深300股指期货中的看涨操作&#xff1a;老板们可以通过买入沪深300股指期货合约&#xff0c;代码也就是…...

使用selenium来获取数据集

使用selenium来获取数据集 1、下载最新的chrome浏览器与chromedriver.exe 查看chrome的版本,打开谷歌浏览器,点击右上角的三个点,然后点击【帮助】, 点击【关于Google Chrome】 然后去下载同样为134版本号的chromedriver.exe, 网址:https://googlechromelabs.github.…...

MCP(大模型上下文协议)

以下是关于大模型MCP协议&#xff08;Model Context Protocol&#xff09;的详细介绍&#xff0c;综合其定义、技术架构、应用场景及行业影响&#xff1a; 一、定义与核心目标 **MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;**是由Anthropic…...

FPGA中串行执行方式之流水线(Pipeline)

FPGA中串行执行方式之流水线(Pipeline) 在FPGA设计中,​流水线(Pipeline)​ 是一种常见的优化技术,用于提高系统的吞吐量和性能。流水线通过将复杂的逻辑分解为多个阶段,每个阶段在一个时钟周期内完成一部分工作,并将中间结果传递到下一阶段。这种方式可以显著提高时钟…...

Python 3.8 Requests 爬虫教程(2025最新版)

遵守网站的爬虫规则、避免爬取敏感信息、保护个人隐私&#xff01; 一、环境配置与基础验证 # 验证 Python 版本&#xff08;需 ≥3.8&#xff09; import sys print(sys.version) # 应输出类似 3.8.12 的信息# 安装 requests 库&#xff08;若未安装&#xff09; # 命令行执…...

【深度学习】GAN生成对抗网络:原理、应用与发展

GAN生成对抗网络&#xff1a;原理、应用与发展 文章目录 GAN生成对抗网络&#xff1a;原理、应用与发展1. 引言2. GAN的基本原理2.1 核心思想2.2 数学表达2.3 训练过程 3. GAN的主要变体3.1 DCGAN (Deep Convolutional GAN)3.2 CGAN (Conditional GAN)3.3 CycleGAN3.4 StyleGAN…...

LINUX基础 [三] - 进程创建

目录 前言 进程创建的初次了解&#xff08;创建进程的原理&#xff09; 什么是fork函数&#xff1f; 初识fork函数 写时拷贝 fork函数存在的意义 fork调用失败的原因 进程终止 运行完毕结果不正确 main函数返回 库函数函数exit 系统调用接口_exit 进程异常终止 进…...

AI比人脑更强,因为被植入思维模型【24】替身决策思维模型

定义 替身决策思维模型是一种在面对复杂问题或决策情境时&#xff0c;通过将自己代入到不同的角色&#xff08;即“替身”&#xff09;中&#xff0c;从这些角色的视角出发去思考、分析和做出决策的思维方式。这种思维模型要求决策者暂时摆脱自身固有的思维定式和立场&#xf…...

数据清洗:基于python抽取jsonl文件数据字段

基于python抽取目录下所有“jsonl”格式文件。遍历文件内某个字段进行抽取并合并。 import os import json import time from tqdm import tqdm # 需要先安装&#xff1a;pip install tqdmdef process_files():# 设置目录路径dir_path r"D:\daku\关键词识别\1623-00000…...

spring后端处理各种请求

在Spring MVC中处理JSON请求和返回JSON消息的步骤如下&#xff1a; 1. 添加依赖 确保项目中包含处理JSON的库&#xff0c;如Jackson。 Maven配置&#xff08;pom.xml&#xff09;&#xff1a; <dependency><groupId>com.fasterxml.jackson.core</groupId>…...

企业级部署zabbix分布式监控系统

目录 一、Zabbix分布式监控系统介绍 1.什么是“Zabbix” 2.Zabbix分布式监控系统的特点 3.Zabbix分布式监控系统的原理 4.Zabbix分布式监控系统的运用 5. Zabbix分布式监控系统的部署顺序 二、搭建 1.设备硬件配置参考 2.zabbix分布式监控系统各节点设备名称和IP规划 …...

OkHttp 的证书设置

在 Android 开发中&#xff0c;通过 OkHttp 自定义 SSLSocketFactory 和 X509TrustManager 可以有效增强 HTTPS 通信的安全性&#xff0c;防止中间人攻击&#xff08;如抓包工具 Charles/Fiddler 的拦截&#xff09;。以下是实现防抓包的关键技术方案&#xff1a; 一、Okhttp设…...

ETL:数据清洗、规范化和聚合的重要性

在当今这个数据呈爆炸式增长的时代&#xff0c;数据已成为企业最为宝贵的资产之一。然而&#xff0c;数据的海量增长也伴随着诸多问题&#xff0c;如数据来源多样、结构复杂以及质量问题等&#xff0c;这些问题严重阻碍了数据的有效处理与深度分析。在此背景下&#xff0c;ETL&…...

蓝桥杯备考:图的遍历

这道题乍一看好像没什么不对的&#xff0c;但是&#xff01;但是&#xff01;结点最大可以到10的5次方&#xff01;&#xff01;&#xff01;我们递归的时间复杂度是很高的&#xff0c;我们正常遍历是肯定通过不了的&#xff0c;不信的话我们试一下 #include <iostream>…...

【多媒体交互】Unity Kinect实现UI控件的点击

在Unity中&#xff0c;通过Kinect实现UI控件的点击功能&#xff0c;主要涉及手部追踪、坐标映射和手势检测三个核心环节。 实现步骤 初始化Kinect与关节追踪 使用KinectManager获取用户ID和手部关节点&#xff08;如JointType.HandLeft&#xff09;的坐标。 long userId _…...

QinQ项展 VLAN 空间

随着以太网技术在网络中的大量部署&#xff0c;利用 VLAN 对用户进行隔离和标识受到很大限制。因为 IEEE802.1Q 中定义的 VLAN Tag 域只有 12 个比特&#xff0c;仅能表示 4096 个 VLAN&#xff0c;无法满足城域以太网中标识大量用户的需求&#xff0c;于是 QinQ 技术应运而生。…...

OBS虚拟背景深度解析:无需绿幕也能打造专业教学视频(附插件对比)

想要录制教学视频却苦于背景杂乱&#xff1f;本文将手把手教你用OBS实现专业级虚拟背景效果&#xff0c;无需绿幕也能轻松营造沉浸式教学场景。文末附6个提升画面质感的免费背景资源&#xff01; 一、虚拟背景的核心价值&#xff1a;从「教师宿舍」到「虚拟讲堂」的蜕变 我们调…...

26考研——图(6)

408答疑 文章目录 一、图的基本概念二、图的存储三、图的遍历四、图的应用五、图的代码实操六、参考资料鲍鱼科技课件26王道考研书 七、总结图的存储结构邻接矩阵邻接表 图的遍历图的相关概念完全图和连通图图的连通性 关键路径学习建议 一、图的基本概念 文章链接: link 二、…...

Redis常用数据类型深度解析:从理论到最佳实践

Redis常用数据类型深度解析&#xff1a;从理论到最佳实践 一、引言二、Redis数据类型全景图三、核心数据类型详解**1. String&#xff08;字符串&#xff09;****2. Hash&#xff08;哈希表&#xff09;****3. List&#xff08;列表&#xff09;****4. Set&#xff08;集合&…...

DeepSeek-V3 模型更新,加量不加价

DeepSeek V3-0324 是 DeepSeek V3 系列的重要升级版本&#xff0c;虽然被官方称为「小版本迭代」&#xff0c;但其在技术能力、开源策略和用户体验上均有显著提升。以下是主要新特性功能和核心变化&#xff1a; 推理能力 基准测试性能显著提升&#xff1a; MMLU-Pro&#xff1…...

Vue项目的 Sass 全局基础样式格式化方案,包含常见元素的样式重置

步骤 1&#xff1a;创建全局样式文件 在项目中创建文件&#xff1a;src/assets/scss/global.scss 内容如下&#xff1a; // 全局盒模型设定&#xff08;边框计入宽高&#xff09; *, *::before, *::after {box-sizing: border-box;margin: 0;padding: 0; }// 基础元素样式重置…...

【Spring篇】Spring的生命周期

一、Bean 生命周期的核心阶段 1. 实例化&#xff08;Instantiation&#xff09; • 触发时机&#xff1a;容器启动时&#xff08;单例 Bean&#xff09;或请求时&#xff08;原型 Bean&#xff09;。 • 实现方式&#xff1a; 通过反射&#xff08;Class.newInstance() 或构造…...

Qt中通过QLabel实时显示图像

Qt中的QLabel控件用于显示文本或图像&#xff0c;不提供用户交互功能。以下测试代码用于从内置摄像头获取图像并实时显示&#xff1a; Widgets_Test.h&#xff1a; class Widgets_Test : public QMainWindow {Q_OBJECTpublic:Widgets_Test(QWidget *parent nullptr);~Widgets…...

[数据结构]1.时间复杂度和空间复杂度

这里写目录标题 1. 算法复杂度2. 时间复杂度2.1 执行次数2.2 大O渐进表示法2.3 常见时间复杂度计算eg1eg2eg3eg4eg5eg6eg7eg8eg9 3. 空间复杂度eg1eg2eg3eg4 4. 常见复杂度对比5. 复杂度练习eg1 1. 算法复杂度 衡量一个算法的好坏&#xff0c;一般是从时间空间两个维度来衡量&…...

【每日算法】Day 6-1:哈希表从入门到实战——高频算法题(C++实现)

摘要 &#xff1a;掌握高频数据结构&#xff01;今日深入解析哈希表的核心原理与设计实现&#xff0c;结合冲突解决策略与大厂高频真题&#xff0c;彻底掌握O(1)时间复杂度的数据访问技术。 一、哈希表核心思想 哈希表&#xff08;Hash Table&#xff09; 是一种基于键值对的…...

物联网平台架构介绍

物联网是连接物理设备、传感器、软件等的网络系统&#xff0c;使设备能够自动收集、交换和处理数据&#xff0c;实现智能化识别、定位、跟踪、监控和管理。随着物联网技术的飞速发展&#xff0c;物联网平台架构的设计变得至关重要&#xff0c;它决定了物联网系统的性能、可扩展…...

TCP/IP三次握手的过程,为什么要3次?

一&#xff1a;过程 第一次&#xff08;SYN&#xff09;&#xff1a; 客户端发送一个带有SYN标志的TCP报文段给服务器&#xff0c;设置SYN1&#xff0c;并携带初始序列号Seqx&#xff08;随机值&#xff09;&#xff0c;进入SYN_SENT状态。等待服务器相应。 第二次&#xff08…...

开源模型应用落地-语音转文本-whisper模型-AIGC应用探索(四)

一、前言 语音转文本技术具有重要价值。它能提高信息记录和处理的效率,使人们可以快速将语音内容转换为可编辑、可存储的文本形式,方便后续查阅和分析。在教育领域,可帮助学生更好地记录课堂重点;在办公场景中,能简化会议记录工作。同时,该技术也为残障人士提供了便利,让…...

Qt开发:QInputDialog的使用

文章目录 一、QInputDialog的介绍二、 QInputDialog的基本用法三、使用 QInputDialog的实例四、QInputDialog的信号与槽 一、QInputDialog的介绍 QInputDialog 是 Qt 提供的一个对话框类&#xff0c;用于获取用户输入的文本、整数或浮点数。它提供了简单易用的静态方法和可定制…...

【系统架构设计师】软件质量管理

目录 1. 说明2. 软件质量保证2.1 说明2.2 质量保证的主要目标2.3 目标2.4 主要作用2.5 主要任务 3. 软件质量保证3.1 说明3.2 ISO 90003.3 CMM 4. 例题4.1 例题1 1. 说明 1.软件质量就是软件与明确地和隐含地定义的需求相一致的程度&#xff0c;更具体地说&#xff0c;软件质量…...

医院挂号预约小程序|基于微信小程序的医院挂号预约系统设计与实现(源码+数据库+文档)

医院挂号预约小程序 目录 基于微信小程序的医院挂号预约系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、小程序用户端 2、系统服务端 &#xff08;1&#xff09; 用户管理 &#xff08;2&#xff09;医院管理 &#xff08;3&#xff09;医生管理 &#xf…...

UE4-UE5虚幻引擎,前置学习一--Console日志输出经常崩溃,有什么好的解决办法

有些差异 这么牛逼的引擎&#xff0c;居然有这种入门级别的问题&#xff0c;一触发清理&#xff0c;大概率(80%)会崩溃 无论虚幻5还是UE4都有这个问题&#xff0c;挺烦人的 实在忍不了了&#xff0c;这次&#xff0c;今天 就想问问有什么好的处理方法么&#xff1f;&#x…...

javaSE.多维数组

1 final 引用类型 final int[] arr 继承Object 的引用类型&#xff0c;不能改变引用的对象 存的其实是引用 数组类型数组&#xff0c;其实存的是引用 int [][] arr new int[][] { {1,2,3}, {4,5,6} };int [] a arr[0]; int [] b arr[1];...

Linux输入系统应用编程

什么是输入系统 Linux 输入系统是处理用户输入设备(如键盘、鼠标、触摸屏、游戏手柄等)的软件架构。在应用编程层面&#xff0c;它提供了与这些输入设备交互的接口。 主要组成部分 输入设备驱动层&#xff1a;直接与硬件交互的驱动程序 输入核心层&#xff1a;内核中的输入子…...

leetcode11.盛水最多的容器

双指针问题&#xff0c;指向前后边界&#xff0c;每次只移动高度较小的那个 class Solution { public:int maxArea(vector<int>& height) {int leftIndex0,rightIndexheight.size()-1;int result0;while(leftIndex<rightIndex){resultmax(result,(rightIndex-lef…...

ngx_http_index_loc_conf_t

定义在 src\http\modules\ngx_http_index_module.c typedef struct {ngx_array_t *indices; /* array of ngx_http_index_t */size_t max_index_len; } ngx_http_index_loc_conf_t; ngx_http_index_loc_conf_t 是 Nginx 中用于管理 index 指…...

[C++面试] 你了解视图吗?

一、入门 1、什么是 C 视图&#xff08;View&#xff09;&#xff1f;请简要说明其概念和用途 它提供了对序列&#xff08;如数组、容器等&#xff09;的非拥有性、只读或可写的访问。&#xff08;就像是个透明的放大镜&#xff0c;它能让你去看一组数据&#xff0c;但它自己…...

NetMizer-日志管理系统-远程命令执行漏洞挖掘

漏洞描述&#xff1a;NetMizer 日志管理系统 cmd.php中存在远程命令执行漏洞&#xff0c;攻击者通过传入 cmd参数即可命令执行 1.fofa搜素语句 title"NetMizer 日志管理系统" 2.漏洞验证 网站页面 验证POC /data/manage/cmd.php?cmdid...

UDP通信实现

一、Socket简介&#xff08;套接字) TCP/IP 五层网络模型的应用层编程接口称为Socket API, Socket( 套接字 ) &#xff0c;它是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。 一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换…...

Browserlist 使用指南:应对浏览器兼容性问题的解决方案

前言 在前端开发中&#xff0c;我们经常需要处理各种不同的浏览器兼容性问题。每个浏览器的版本众多&#xff0c;处理这些问题可能会让人感到头疼。幸运的是&#xff0c;有一个名为 Browserlist 的工具可以大大简化这项工作。本文将介绍 Browserlist 的作用和使用方法&#xf…...

[蓝桥杯 2023 省 A] 异或和之和

题目来自洛谷网站&#xff1a; 暴力思路&#xff1a; 先进性预处理&#xff0c;找到每个点位置的前缀异或和&#xff0c;在枚举区间。 暴力代码&#xff1a; #include<bits/stdc.h> #define int long long using namespace std; const int N 1e520;int n; int arr[N…...

ABC391题解

A 算法标签: 模拟 #include <iostream> #include <algorithm> #include <cstring> #include <map>using namespace std;const int N 8; map<string, string> mp;int main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);mp.insert({…...