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

参考

相关推荐
萧雾宇27 分钟前
Android Compose打造仿现实逼真的烟花特效
android·flutter·kotlin
翻滚丷大头鱼1 小时前
android 性能优化—ANR
android·性能优化
翻滚丷大头鱼1 小时前
android 性能优化—内存泄漏,内存溢出OOM
android·性能优化
拜无忧1 小时前
【教程】flutter常用知识点总结-针对小白
android·flutter·android studio
拜无忧2 小时前
【教程】Flutter 高性能项目架构创建指南:从入门到高性能架构
android·flutter·android studio
用户2018792831672 小时前
故事:公司的 "私人储物柜" 系统(ThreadLocalMap)
android·java
CYRUS_STUDIO2 小时前
如何防止 so 文件被轻松逆向?精准控制符号导出 + JNI 动态注册
android·c++·安全
yinmaisoft3 小时前
当低代码遇上AI,有趣,实在有趣
android·人工智能·低代码·开发工具·rxjava
如此风景3 小时前
Compose Modifier 修饰符介绍
android
纽马约3 小时前
Android BaseQuickAdapter的使用
android