ViewModel
ViewModel的原理是系统配置更改情况下在onDestroy时Actvitity中保存ViewModel的viewModelStore并不会被销毁(非配置更改的情况会销毁viewModelStore)。重建后在获取ViewModel时会根据传入的类全限定名从ViewModelStore中取;
上述并不能针对内存不足而Activity被回收的情况下生效,针对内存不足回收activity的情况,需要手动在onSaveInstanceState中存储
之后手动在onRestoreInstanceState中取,或者是使用SavedStateHandle封装了这个过程
简述:fragment
或componentActivity
实现了ViewModelStoreOwner
接口,实现该接口方法getViewModelStore()
,当调用ViewModelProvider(ViewModelStoreOwner owner).get(Class<T> modelClass)
时,会使用工厂模式创建viewModel
,之后将其以canonicalName(全限定名)
为key存入mViewModelStore
中,ViewModelStore
内部是个HashMap<String, ViewModel>
。
当屏幕旋转或切换系统语言等配置修改的行为发生时,Activity
生命周期从销毁再重建,在销毁时(配置修改),如果判断系统配置没有变化(即!isChangingConfigurations
)清空保存的ViewModel
(即 getViewModelStore().clear();
)
如果发生变化则调用onRetainNonConfigurationInstance()
方法将 viewModelStore
保存起来,当Activity重建时则从getLastNonConfigurationInstance()
中获取保存的mViewModelStore
ps:如果ViewModelProvider传入Activity,则取得是Activity的ViewModelStore,如果传入了fragment,则根据以下代码取ViewModelStore,即先取父fragment的FragmentManager的ViewModelStore,再取hostActivty的ViewModelStore,最后才是新建一个。
1 | if (parent != null) { |
当屏幕旋转或者切换系统语言时,Activity
生命周期从销毁再重建,但是ViewModel
里面的变量值不受到影响,说明ViewModel中的变量在屏幕旋转前进行了存储,在屏幕旋转后又进行了恢复。
里面的原理是怎么实现的呢?
一、获取ViewModel实例
1 | // MainActivity.kt |
这个代码拆分成2段来分析:ViewModelProvider(this)
和get(MainViewModel::class.java)
二、ViewModelProvider(this)
用于获取 ViewModelProvider
实例
1 | // ViewModelProvider.java |
代码很简单,可以看出,最后调用的是ComponentActivity
中的ensureViewModelStore()
方法,这个方法很重要。
这个方法涉及2个很重要的类:ViewModelStore
和 NonConfigurationInstances
。
ViewModelStore
ViewModelStore
从名字可以看出,是用来存储ViewModle
对象的,做一个缓存的作用,底层用Map实现。源码:
1 | // ViewModelStore.java |
代码很简单,可以看出ViewModelStore
里面就是一个HashMap
,用于缓存ViewModel
实例对象。
NonConfigurationInstances
1 | // ComponentActivity$NonConfigurationInstances.java |
这其实就是一个Java Bean类,里面存在2个字段,包过 viewModelStore
字段。
三、get(MainViewModel::class.java)
1 | // ViewModelProvider.java |
先根据key从mViewModelStore
获取缓存中的ViewModel
,如果存在,则返回viewModel
实例。
如果mViewModelStore
缓存中不存在当前modelClass
的实例,则用工厂方法创建一个,再将新创建的加入缓存。
我们在Activity
中并没有设置key,默认的key又是一个常量,猜测:
屏幕旋转前后,mViewModelStore
应该是同一个对象,得到的跟viewModel
也是同一份实例对象。 所以我们只要找出屏幕旋转前后,mViewModelStore
如何保存和恢复即可。
验证猜测最直接的方法就是log打印:
1 | val viewModel = ViewModelProvider(this).get(MainViewModel::class.java) |
屏幕旋转后,mViewModelStore和viewModel对象地址确实是同一个。
四、mViewModelStore 的恢复
获取 mViewModelStore
代码如下:
1 | // ComponentActivity.java |
屏幕旋转前后,mViewModelStore
在屏幕旋转前后都是同一个对象,这个对象不可能是new出来的,那就是走的 mViewModelStore = nc.viewModelStore;
,也就是从getLastNonConfigurationInstance()
得到的屏幕旋转前保存的数据。
屏幕旋转后,Activity
重建后从 getLastNonConfigurationInstance()
中获取到了屏幕旋转前保存的 NonConfigurationInstances
实例对象,然后从nc
对象中获取存储的mViewModelStore
对象。
我们一般是在 onCreate()
中去获取ViewModel
实例对象的,说明getLastNonConfigurationInstance()
这个方法在 onCreate()
方法前调用。
那当屏幕旋转前, mViewModelStore
实例是在哪存储的呢?
五、mViewModelStore 的存储
从上面可以看出,屏幕旋转完成,Activity
重建后mViewModelStore
是从NonConfigurationInstances
获取的,那屏幕旋转前肯定也是在这里存储的。
搜索调用的地方:
从上图片可以看到是在第二处,代码如下:
1 | // ComponentActivity.java |
可以得出结论:屏幕旋转前,数据在 onRetainNonConfigurationInstance()
保存,Activity 重建后,在 getLastNonConfigurationInstance()
中恢复。
Activity生命周期调用如下:
继续跟一下源码,寻找数据具体存储在哪里?
六、一探到底
Activity重启后数据的恢复
Activity 重建后,在 getLastNonConfigurationInstance()
中恢复。
1 | // Activity.java |
mLastNonConfigurationInstances
赋值的地方:
1 | // Activity.java |
从Activity的启动流程可知,Activity$attach()
方法是在ActivityThread
调用的:
1 | // ActivityThread.java |
从上得知,数据存储在ActivityClientRecord
中,在Activity
启动时将ActivityClientRecord
中的lastNonConfigurationInstances
通过attach()
方法赋值到对应的Activity
中,然后通过getLastNonConfigurationInstance()
恢复数据。
屏幕旋转前数据的存储
屏幕旋转前,数据在 onRetainNonConfigurationInstance()
保存。
在Activity
的retainNonConfigurationInstances()
方法中被调用。
那retainNonConfigurationInstances()
方法又是在哪调用的呢?肯定也跟ActivityThread
有关,在ActivityThread
搜索下,代码如下:
1 | // ActivityThread.java |
从上得知,performDestroyActivity()
调用了retainNonConfigurationInstances()
方法并把数据保存到了ActivityClientRecord
的lastNonConfigurationInstances
中。
七、特例-系统杀后台
由于上文已经做过实验了,我这里直接贴上实验的打印结果 第一张图是模拟杀后台的生命周期打印,第二张图是屏幕旋转。
可以看出:
系统杀后台,Activity不会走onDestory()
和onRetainCustomNonConfigurationInstance()
方法。 可以说杀掉后台,Activity销毁的生命周期都不会走,只有App再回到前台时,才会走Activity重建生命周期。
因为没有执行onRetainCustomNonConfigurationInstance()
方法,Activity的数据也没有缓存下来,所以Activity重建也没有数据可以恢复。
下图是Activity中的ViewModel实例对象地址打印:
可以看出,adb模拟杀掉后台后,ViewModel地址值变了,是一个全新的地址。
如果想要系统内存不足,杀掉后台,App再次回到前台,之前的数据进行恢复,应该怎么处理?
请听下回分析。😁
八、总结
屏幕旋转前,Activity销毁时:
ComponentActivity
调用onRetainNonConfigurationInstance()
方法,将要销毁的Activity
的mViewModelStore
转化为NonConfigurationInstances
对象,继续调用Activity
的retainNonConfigurationInstances()
方法,最终在ActivityThread
的performDestroyActivity()
中将数据保存在ActivityClientRecord
中。
Activity重建后:
在Activity
启动时,ActivityThread
调用performLaunchActivity()
方法,将存储在ActivityClientRecord
中的lastNonConfigurationInstances
通过Activity
的attach()
方法传递到对应的Activity
中,然后通过getLastNonConfigurationInstance()
恢复mViewModelStore
实例对象,最后根据对应的key
拿到销毁前对应的ViewModel
实例。
此外,当系统内存不足,系统将后台应用回收后,ViewModel中的数据不会恢复。
附上总体流程图: