简述: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 2 3 4 5 6 7 8 if (parent != null ) { this .mNonConfig = parent.mFragmentManager.getChildNonConfig(parent); } else if (host instanceof ViewModelStoreOwner) { ViewModelStore viewModelStore = ((ViewModelStoreOwner)host).getViewModelStore(); this .mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore); } else { this .mNonConfig = new FragmentManagerViewModel(false ); }
当屏幕旋转或者切换系统语言时,Activity
生命周期从销毁再重建,但是ViewModel
里面的变量值不受到影响,说明ViewModel中的变量在屏幕旋转前进行了存储,在屏幕旋转后又进行了恢复。
里面的原理是怎么实现的呢?
一、获取ViewModel实例 1 2 val viewModel = ViewModelProvider(this ).get (MainViewModel::class .java)
这个代码拆分成2段来分析:ViewModelProvider(this)
和get(MainViewModel::class.java)
二、ViewModelProvider(this) 用于获取 ViewModelProvider
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public ViewModelProvider (@NonNull ViewModelStoreOwner owner) { this (owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelStore getViewModelStore () { if (getApplication() == null ) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call." ); } ensureViewModelStore(); return mViewModelStore; } @SuppressWarnings("WeakerAccess") void ensureViewModelStore () { if (mViewModelStore == null ) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null ) { mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null ) { mViewModelStore = new ViewModelStore(); } } }
代码很简单,可以看出,最后调用的是ComponentActivity
中的ensureViewModelStore()
方法,这个方法很重要。
这个方法涉及2个很重要的类:ViewModelStore
和 NonConfigurationInstances
。
ViewModelStore ViewModelStore
从名字可以看出,是用来存储ViewModle
对象的,做一个缓存的作用,底层用Map实现。源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put (String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null ) { oldViewModel.onCleared(); } } final ViewModel get (String key) { return mMap.get(key); } Set<String> keys () { return new HashSet<>(mMap.keySet()); } public final void clear () { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } }
代码很简单,可以看出ViewModelStore
里面就是一个HashMap
,用于缓存ViewModel
实例对象。
NonConfigurationInstances 1 2 3 4 5 static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; }
这其实就是一个Java Bean类,里面存在2个字段,包过 viewModelStore
字段。
三、get(MainViewModel::class.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private static final String DEFAULT_KEY ="androidx.lifecycle.ViewModelProvider.DefaultKey" ;public <T extends ViewModel> T get (@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); if (canonicalName == null ) { throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels" ); } return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } public <T extends ViewModel> T get (@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } return (T) viewModel; } else { if (viewModel != null ) { } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); } mViewModelStore.put(key, viewModel); return (T) viewModel; }
先根据key从mViewModelStore
获取缓存中的ViewModel
,如果存在,则返回viewModel
实例。
如果mViewModelStore
缓存中不存在当前modelClass
的实例,则用工厂方法创建一个,再将新创建的加入缓存。
我们在Activity
中并没有设置key,默认的key又是一个常量,猜测:
屏幕旋转前后,mViewModelStore
应该是同一个对象,得到的跟viewModel
也是同一份实例对象。 所以我们只要找出屏幕旋转前后,mViewModelStore
如何保存和恢复即可。
验证猜测最直接的方法就是log打印:
1 2 val viewModel = ViewModelProvider(this ).get (MainViewModel::class .java)Log.i("wutao--> " , "viewModel--> $viewModel " + " getViewModelStore--> ${getViewModelStore()} " )
屏幕旋转后,mViewModelStore和viewModel对象地址确实是同一个。
四、mViewModelStore 的恢复 获取 mViewModelStore
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void ensureViewModelStore () { if (mViewModelStore == null ) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null ) { mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null ) { mViewModelStore = new ViewModelStore(); } } }
屏幕旋转前后,mViewModelStore
在屏幕旋转前后都是同一个对象,这个对象不可能是new出来的,那就是走的 mViewModelStore = nc.viewModelStore;
,也就是从getLastNonConfigurationInstance()
得到的屏幕旋转前保存的数据。
屏幕旋转后,Activity
重建后从 getLastNonConfigurationInstance()
中获取到了屏幕旋转前保存的 NonConfigurationInstances
实例对象,然后从nc
对象中获取存储的mViewModelStore
对象。
我们一般是在 onCreate()
中去获取ViewModel
实例对象的,说明getLastNonConfigurationInstance()
这个方法在 onCreate()
方法前调用。
那当屏幕旋转前, mViewModelStore
实例是在哪存储的呢?
五、mViewModelStore 的存储 从上面可以看出,屏幕旋转完成,Activity
重建后mViewModelStore
是从NonConfigurationInstances
获取的,那屏幕旋转前肯定也是在这里存储的。
搜索调用的地方:
从上图片可以看到是在第二处,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public final Object onRetainNonConfigurationInstance () { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null ) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null ) { viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null ) { return null ; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; }
可以得出结论:屏幕旋转前,数据在 onRetainNonConfigurationInstance()
保存,Activity 重建后,在 getLastNonConfigurationInstance()
中恢复。
Activity生命周期调用如下:
继续跟一下源码,寻找数据具体存储在哪里?
六、一探到底 Activity重启后数据的恢复 Activity 重建后,在 getLastNonConfigurationInstance()
中恢复。
1 2 3 4 5 public Object getLastNonConfigurationInstance () { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null ; }
mLastNonConfigurationInstances
赋值的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 final void attach (Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { attachBaseContext(context); ··· mLastNonConfigurationInstances = lastNonConfigurationInstances; ··· }
从Activity的启动流程可知,Activity$attach()
方法是在ActivityThread
调用的:
1 2 3 4 5 6 7 8 9 10 11 12 Activity.NonConfigurationInstances lastNonConfigurationInstances; private Activity performLaunchActivity (ActivityClientRecord r, Intent customIntent) { activity.attach(appContext, this , getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); }
从上得知,数据存储在ActivityClientRecord
中,在Activity
启动时将ActivityClientRecord
中的lastNonConfigurationInstances
通过attach()
方法赋值到对应的Activity
中,然后通过getLastNonConfigurationInstance()
恢复数据。
屏幕旋转前数据的存储 屏幕旋转前,数据在 onRetainNonConfigurationInstance()
保存。
在Activity
的retainNonConfigurationInstances()
方法中被调用。
那retainNonConfigurationInstances()
方法又是在哪调用的呢?肯定也跟ActivityThread
有关,在ActivityThread
搜索下,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ActivityClientRecord performDestroyActivity (IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); ··· if (getNonConfigInstance) { try { r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { ··· } } ··· return r; }
从上得知,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中的数据不会恢复。
附上总体流程图: