深入理解Jetpack——ViewModel

ViewModel的作用

ViewModel 的作用其实很简单,就是在屏幕旋转时,可以让数据继续存留。ViewModel 的生命周期如下图所示,可以看到当屏幕旋转时,并不会影响到 ViewModel。

ViewModel的使用

kotlin 复制代码
class MyViewModel: ViewModel() {

    //回收资源
    override fun onCleared() {
        super.onCleared()
    }

}

val viewModel: MyViewModel by viewModels<MyViewModel>()

ViewModel 的使用也很简单。如上面的代码所示,我们继承 ViewModel 类,在 Activity中通过 viewModels 扩展方法来获取对应的对象,最后当 Activity 销毁时在 onCleared 方法中释放资源。

如果你希望在ViewModel中使用Context,则可以使用 AndroidViewModel 类,它继承自 ViewModel ,并接收Application 作为 Context。代码如下:

kotlin 复制代码
//AndroidViewModel 相比 ViewModel 多了一个 Application
class MyAndroidViewModel(application: Application): AndroidViewModel(application) {

    //回收资源
    override fun onCleared() {
        super.onCleared()
    }

}

val androidViewModel: MyAndroidViewModel by viewModels<MyAndroidViewModel>()

如果你的 ViewModel 有其他依赖,这时就需要自定义 Factory。最简单的方式就是给自定义的 Factory 加上入参,代码示例如下:

kotlin 复制代码
val viewModel: MyViewModel by viewModels<MyViewModel>(  
    factoryProducer = { MyViewModelFactory("tag") }  //设置自定义的 Factory
)

但是这种方式代码太耦合了, Google 更推荐使用 CreationExtras 的方式。代码示例如下:

kotlin 复制代码
//MyViewModel 需要 tag 作为入参
class MyViewModel(val tag: String): ViewModel() {

    companion object {

        private object Tag: CreationExtras.Key<String>
        
        //CreationExtras 中的 Key
        val TAG_KEY: CreationExtras.Key<String> = Tag

        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val tag = this[TAG_KEY] ?: ""
                MyViewModel(tag)
            }
        }
    }

}

val viewModel: MyViewModel by viewModels<MyViewModel>(
        //设置 CreationExtras 的参数,作为入参
        extrasProducer = { MutableCreationExtras().also { it.set(MyViewModel.TAG_KEY, "tag") }}, 
        //设置自定义的 Factory
        factoryProducer = { MyViewModel.Factory }
    )

在实际开发中,经常会遇到两个Fragment之间有通信的需求。这时我们可以通过共享 ViewModel 来实现这一个功能。

首先我们先增加依赖:

scss 复制代码
implementation("androidx.fragment:fragment-ktx:1.6.2")

然后我们就可以使用 activityViewModels 来获取当前 Activity 下的指定的 ViewModel。注意不能使用 viewModels ,不然获取的 ViewModel 是其对应 Fragment 下的对象,而不是同一个。代码示例如下:

kotlin 复制代码
//可以获取到同一个 MyViewModel 对象
class AFragment: Fragment() {  
  
    val viewModel: MyViewModel by activityViewModels<MyViewModel>()
    
    //不能使用viewModels,不然获取的 ViewModel 是其对应 Fragment 下的对象,而不是同一个
    //val viewModel: MyViewModel by viewModels<MyViewModel>()
    
}

class BFragment: Fragment() {  
  
    val viewModel: MyViewModel by activityViewModels<MyViewModel>()
    
}

Fragment 里面的 viewModels 相对于 Activity 下的多了一个 ownerProducer 参数。这个参数可以指定 ViewModel 的存储地点(即ViewModelStoreOwner对象)。代码如下所示:

kotlin 复制代码
val viewModel: MyViewModel by viewModels<MyViewModel>( 
    //存储地点为父Fragment
    ownerProducer = { requireParentFragment() },  
    //存储地点为activity
    //ownerProducer = { requireActivity() },  
    //存储地点为FragmentB()
    //ownerProducer = { FragmentB() },  
)

ViewModel的原理

如上图所示,当我们第一次通过 viewModels 获取对应的 ViewModel 对象时,其内部首先会先创建 ViewModelProvider 对象, 然后在 Factory(可以自定义Factory) 使用传入的 Class 来反射获取对应的 ViewModel 实例,最后将实例存储在 ViewModelStore 中。

ViewModelStore 内部是一个 map,通过 key-value 保存 ViewModel 对象。而 ViewModelStoreOwner 只是一个接口,activity 和 Fragment 都实现了它,因此我们可以在activity 和 Fragment 中使用 ViewModel。

当我们下一次获取 ViewModel 时,就会直接从 ViewModelStore 中获取对应的 ViewModel 对象,而不是反射获取了。

上面提到过,ViewModel 最大的特点就是在屏幕旋转时,可以让数据继续存留。它的核心原理是NonConfigurationInstances 类。

如上图所示,ComponentActivity 的 NonConfigurationInstances 类中包含着 ViewModelStore 对象。而 Activity 的 NonConfigurationInstances 类也引用着 ComponentActivity 的 NonConfigurationInstances 类对象。当 Activity 在屏幕旋转情况下重建时,ActivityThread 的 performDestroyActivity 方法中会保存 Activity 的 NonConfigurationInstances 对象到 ActivityClientRecord 中。而当 Activity 重建完成时,ActivityThread 的 performLaunchActivity 方法将会被调用。它会将 Activity 的 NonConfigurationInstances 对象作为参数传入到 attach 方法中去。

ViewModel 与 onSaveInstanceState 的区别

除了 ViewModel,onSaveInstanceState()方法同样可以解决屏幕旋转带来的数据丢失问题。那么它们的区别是什么呢?

  1. 调用时机上,当系统"未经你许可"时销毁了你的activity时就会调用 onSaveInstanceState,比如A 跳转 B、按下Home键等;而使用ViewModel恢复数据则只有在因配置更改界面销毁重建的情况。
  2. 存储方式上,onSaveInstanceState 是通过序列化到磁盘的方式,而 ViewModel 是存储在内存中
  3. 存储数据大小上,ViewModel 是存储在内存中,因此大小限制就是App的可用内存大小。而 onSaveInstanceState 只能存可序列化和反序列化的对象,且大小有限制(一般Bundle限制大小1M)

如果你想要ViewModel感知onSaveInstanceState 的触发,可以使用 SavedStateHandle 组件。这里先不详细介绍,在下一篇 LiveData 的文章中在介绍它。

参考

相关推荐
Dingdangr8 分钟前
Android中的Intent的作用
android
技术无疆9 分钟前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP12 分钟前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy7 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯8 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h10 小时前
Android中的单例模式
android·单例模式
计算机源码社15 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽15 小时前
重修设计模式-结构型-门面模式
android
晨春计17 小时前
【git】
android·linux·git
标标大人18 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言