一、什么是ViewModel?
Android ViewModel在我们使用MVVM开发模式时会经常用到,对我来说就是好用,好维护。
它相对于MVC模式,
一来可以减少Activity层的代码,可以把一些业务逻辑和对数据的交互到ViewModel层去,其次还解决了两个问题:
- Activity配置更改重建时(比如屏幕旋转)保留数据
- UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享。
第一个问题:
在我们某些App的界面,按照业务需求是需要进行屏幕翻转的。所以就会发生activity 重新创建。
当发生配置变更时,系统会重新创建 Activity。为此,系统会调用 onDestroy() 并销毁现有的 Activity 实例。随后,系统会使用 onCreate() 创建一个新实例,并且这个新的 Activity 实例会使用更新后的新配置进行初始化。这也意味着,系统还会使用新配置重新创建界面。
重新创建行为会自动利用与新设备配置相匹配的备用资源来自动重新加载应用,从而帮助它适应新配置。
重新创建过程还会清除您在 Activity 或其包含的 Fragment、View 或其他对象中,以字段形式保留的任何状态。这是因为 Activity 重新创建过程会创建 Activity 和界面的全新实例。此外,之前的旧 Activity 不再可见或不再有效,因此对该 activity 或其所含对象的任何其余引用都已过时。它们会导致 bug、内存泄漏和崩溃。
这时候,不使用VM的情况下只能通过onSaveInstanceState
保存数据,当activity重建后再通过onCreate
或onRestoreInstanceState
方法的bundle中取出,但如果数据量较大,数据的序列化和反序列化将产生一定的性能开销。
第二个问题:
不用VM,各个UI组件都要持有共享数据的引用,这会带来两个麻烦,第一,如果新增了共享数据,各个UI组件需要再次声明并初始化新增的共享数据;第二,某个UI组件对共享数据修改,无法直接通知其他UI组件,需手动实现观察者模式。而ViewModel结合LiveData就可以很轻松的实现这一点。
二、ViewModel的使用
1.引入viewmodel(app下的build.gradle)
Groovy
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
//implementation "android.arch.lifecycle:extensions:1.1.1"
2.创建ViewModel
Kotlin
val viewModel = ViewModelProviders.of(this).get(AndroidViewModel::class.java)
或者
val mainViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
创建流程:
- ViewModelStore是存储VM的数据单元,存储结构为Map,Fragment/FragmentActivity持有其引用。(存储数据,在get方法里首先会判断是否已存在该vm)
- ViewModelProvider通过get方法创建一个VM,创建之前会先检查ViewModelStore中是否存在,若不存在则通过反射创建一个VM。
三、ViewModel的回收
下面是vm的生命周期官方图,可以看到,它的生命周期算比较长的。当一个activity调用onestroy方法时,正常情况下,会自动走到onClear()方法。
在activity的源码里,可以看到监听到生命周期为销毁时,会去判断是否是更改了配置,如果不是才会去清除
Fragment里稍微深一些:如果因为Activity被嘎调调用Destroy
第一步,看FragementActivity销毁方法://所有的fragment收到销毁的消息
第二步:FragmentStateManager里(如果fragment的replace方法导致被替换的fragment嘎调也会走到这里来)
viewmodel的生命周期:
4.配置变化的数据保存(其实就是不清除 )
VM在Activity因配置变化导致重建时会被保留,从生命周期的角度来说,ViewModel的生命周期可能会长于Activity的生命周期。
所以使用ViewModel时一定要注意,不能让其引用Activity或View,否则可能导致内存泄漏。
四、ViewModel的具体实现
这儿贴下我在项目中使用ViewModel的代码,因为上面只能说是对他的一个学习。
启屏页面的viewmodel(注意:因为我在这里面引用了activity,可能造成内存泄漏,所以使用了弱引用)
我的splash界面
使用 private val splashViewModel: AppSplashViewModel by viewModels()声明viewmodel,然后在oncreate下开始倒计时的方法