深入理解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 的文章中在介绍它。

参考

相关推荐
zimoyin4 小时前
Java/Kotlin HashMap 等集合引发 ConcurrentModificationException
java·kotlin
zimoyin4 小时前
Kotlin 使用 Springboot 反射执行方法并自动传参
spring boot·后端·kotlin
dal118网工任子仪10 小时前
91,【7】 攻防世界 web fileclude
android·前端
taopi202410 小时前
android java 用系统弹窗的方式实现模拟点击动画特效
android
fanged10 小时前
Android学习19 -- 手搓App
android
dal118网工任子仪10 小时前
99,[7] buuctf web [羊城杯2020]easyphp
android·前端·android studio
村口老王15 小时前
鸿蒙开发——应用程序包及模块化设计
android·前端·harmonyos
6v6博客15 小时前
如何在 Typecho 中实现 Joe 编辑器标签自动填充
android·编辑器
程序员牛肉19 小时前
为什么网络上一些表情包在反复传播之后会变绿?“电子包浆”到底是怎么形成的?
android
志尊宝19 小时前
Android 深入探究 JSONObject 与 JSONArray:Android 中的数据解析与数组操作全解析
android