ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
ViewModel 的优势
ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。
ViewModel 允许数据在 ViewModel 持有的状态和 ViewModel 触发的操作结束后继续存在。这种缓存意味着在常见的配置更改(例如屏幕旋转)完成后,您无需重新提取数据。
实例化 ViewModel 时,您会向其传递实现 ViewModelStoreOwner 接口的对象。它可能是 Navigation 目的地、Navigation 图表、activity、fragment 或实现接口的任何其他类型。然后,ViewModel 的作用域将限定为 ViewModelStoreOwner
的 Lifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner
永久消失。
有一系列类是 ViewModelStoreOwner
接口的直接或间接子类。直接子类为 ComponentActivity、Fragment 和 NavBackStackEntry。
当 ViewModel 的作用域 fragment 或 activity 被销毁时,异步工作会在作用域限定到该 fragment 或 activity 的 ViewModel 中继续进行。这是持久性的关键。
viewModel是一个类,我们在使用的时候,只要继承就可以了,但是viewModel和UI数据更新是通过LiveData,这就是所谓的MVVM的核心所在。
class DiceRollViewModel : ViewModel() {
private var data: MutableLiveData?=null
fun getData(): MutableLiveData {
if (data == null) {
data = MutableLiveData()
}
return data!!
}
fun updateValue(){
val value=kotlin.random.Random.nextInt(1,100);
data?.value="随机生成数为${value}"
}
}
使用viewModel的核心是想通过viewModel来管理生命周期,所以创建的时候不是通过直接new出来,而是有专门的工具ViewModelProvider
两种构造如下:
public constructor(owner: ViewModelStoreOwner)
public constructor(owner: ViewModelStoreOwner, factory: Factory)
因为fragment和Activity内部已处理了ViewModelStoreOwner,所以可以借助ViewModelProvider直接使用
viewModel = ViewModelProvider(this).get(DiceRollViewModel::class.java)
如果需要factory创建,可以使用factory from的方法
companion object { @JvmStatic fun from(vararg initializers: ViewModelInitializer<*>): Factory = InitializerViewModelFactory(*initializers) }
class TestViewModelActivity : BaseActivity() {
private lateinit var bind: MyVideModelBind
private lateinit var viewModel: DiceRollViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = DataBindingUtil.setContentView(this, R.layout.layout_viewmodel_demo)
viewModel = ViewModelProvider(this).get(DiceRollViewModel::class.java)
viewModel.getData().observe(this, Observer {
bind.value = it
})
bind.btnUpdate.setOnClickListener {
viewModel.updateValue()
}
}
}
ViewModel通常不应引用视图、Lifecycle 或可能存储对 activity 上下文的引用的任何类。由于 ViewModel 的生命周期大于界面的生命周期,因此在 ViewModel 中保留与生命周期相关的 API 可能会导致内存泄漏。
按照依赖项注入的最佳实践,ViewModel 可以在其构造函数中将依赖项作为参数。这大多是网域层或数据层中的类型。由于框架提供 ViewModel,因此需要一种特殊机制来创建 ViewModel 的实例。该机制是 ViewModelProvider.Factory 接口。只有此接口的实现才能在适当的作用域内实例化 ViewModel。
如果 ViewModel 类在其构造函数中接收依赖项,请提供用于实现 ViewModelProvider.Factory 接口的工厂。替换 create(Class
借助 CreationExtras,您可以访问有助于实例化 ViewModel 的相关信息。下面列出了可以通过 extra 访问的键:
键 | 功能 |
---|---|
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY | 提供对您传递给 ViewModelProvider.get() 的自定义键的访问权限。 |
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY | 提供对 Application 类实例的访问权限。 |
SavedStateHandleSupport.DEFAULT_ARGS_KEY | 提供对您在构造 SavedStateHandle 时应使用的参数 bundle 的访问权限。 |
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY | 提供对用于构造 ViewModel 的 SavedStateRegistryOwner 的访问权限。 |
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY | 提供对用于构造 ViewModel 的 ViewModelStoreOwner 的访问权限。 |
如需创建 SavedStateHandle 的新实例,请使用 CreationExtras.createSavedStateHandle().createSavedStateHandle()) 函数并将其传递给 ViewModel。
作用域是有效使用 ViewModel 的关键。每个 ViewModel 的作用域都限定为一个实现 ViewModelStoreOwner 接口的对象。有多个 API 可帮助您更轻松地管理 ViewModel 的作用域。本文档简要介绍了您应该了解的一些关键技术
可以指定哪个fragment或者Activity的主题
val viewModel: MyiewModel by viewModels( ownerProducer = { requireParentFragment() })
关于Navigation怎么使用,可以看我的一篇文章:
Navigation 图也是 ViewModel Store Owner。如果您使用的是 Navigation Fragment 或 Navigation Compose,可以使用 navGraphViewModels(graphId)
val viewModel: MyViewModel by viewModels(
{ findNavController().getBackStackEntry(R.id.nav_graph) }
)
在绑定完Navigation,其内部已支持存储,ViewModelStore
private final HashMapmViewModelStores = new HashMap<>();
Navigation的生命周期,同样可以同步宿主的生命周期。