一、介绍
ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
ViewModel 的优势
ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。
ViewModel 类的主要优势实际上有两个方面:
- 它允许您持久保留界面状态。
- 它可以提供对业务逻辑的访问权限。
持久性
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是一个类,我们在使用的时候,只要继承就可以了,但是viewModel和UI数据更新是通过LiveData,这就是所谓的MVVM的核心所在。
class DiceRollViewModel : ViewModel() {
private var data: MutableLiveData<String>?=null
fun getData(): MutableLiveData<String> {
if (data == null) {
data = MutableLiveData()
}
return data!!
}
fun updateValue(){
val value=kotlin.random.Random.nextInt(1,100);
data?.value="随机生成数为${value}"
}
}
viewModel的创建
使用viewModel的核心是想通过viewModel来管理生命周期,所以创建的时候不是通过直接new出来,而是有专门的工具ViewModelProvider
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,因此需要一种特殊机制来创建 ViewModel 的实例。该机制是 ViewModelProvider.Factory 接口。只有此接口的实现才能在适当的作用域内实例化 ViewModel。
包含 CreationExtras 的 ViewModel
如果 ViewModel 类在其构造函数中接收依赖项,请提供用于实现 ViewModelProvider.Factory 接口的工厂。替换 create(Class<T>, CreationExtras) 函数以提供 ViewModel 的新实例
借助 CreationExtras,您可以访问有助于实例化 ViewModel 的相关信息。下面列出了可以通过 extra 访问的键:
键 | 功能 |
提供对您传递给 | |
提供对 | |
提供对您在构造 | |
提供对用于构造 | |
提供对用于构造 |
如需创建 SavedStateHandle 的新实例,请使用 CreationExtras.createSavedStateHandle().createSavedStateHandle()) 函数并将其传递给 ViewModel。
ViewModel 作用域 API
作用域是有效使用 ViewModel 的关键。每个 ViewModel 的作用域都限定为一个实现 ViewModelStoreOwner 接口的对象。有多个 API 可帮助您更轻松地管理 ViewModel 的作用域。本文档简要介绍了您应该了解的一些关键技术
ViewModel 的作用域限定为最近的 ViewModelStoreOwner
可以指定哪个fragment或者Activity的主题
val viewModel: MyiewModel by viewModels( ownerProducer = { requireParentFragment() })
ViewModel 的作用域限定为 Navigation
关于Navigation怎么使用,可以看我的一篇文章:
Android 导航之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 HashMap<UUID, ViewModelStore> mViewModelStores = new HashMap<>();
以及状态保存saveStateHandle
Navigation的生命周期,同样可以同步宿主的生命周期。