一、介绍

              ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。


ViewModel 的优势

        ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。

ViewModel 类的主要优势实际上有两个方面:

  • 它允许您持久保留界面状态。
  • 它可以提供对业务逻辑的访问权限。

持久性

        ViewModel 允许数据在 ViewModel 持有的状态和 ViewModel 触发的操作结束后继续存在。这种缓存意味着在常见的配置更改(例如屏幕旋转)完成后,您无需重新提取数据。

作用域

实例化 ViewModel 时,您会向其传递实现 ViewModelStoreOwner 接口的对象。它可能是 Navigation 目的地、Navigation 图表、activity、fragment 或实现接口的任何其他类型。然后,ViewModel 的作用域将限定为 ViewModelStoreOwner 的 Lifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。

有一系列类是 ViewModelStoreOwner 接口的直接或间接子类。直接子类为 ComponentActivityFragment 和 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:使用

两种构造如下:

  1. public constructor(owner: ViewModelStoreOwner)
  2. 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()
        }
    }
}


Android MVVM之ViewModel的详解与使用Android 导航之Navigation 组件的介绍与使用_作用域

 结果

 注意:

        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 访问的键:


功能

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 作用域 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) }
    )

源码分析:

Android MVVM之ViewModel的详解与使用Android 导航之Navigation 组件的介绍与使用_android_02

 在绑定完Navigation,其内部已支持存储,ViewModelStore


private final HashMap<UUID, ViewModelStore> mViewModelStores = new HashMap<>();


以及状态保存saveStateHandle

Android MVVM之ViewModel的详解与使用Android 导航之Navigation 组件的介绍与使用_ide_03

 

Navigation的生命周期,同样可以同步宿主的生命周期。