屏幕旋转导致Activity销毁重建,ViewModel是如何恢复数据的
Posted Jason_Lee155
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了屏幕旋转导致Activity销毁重建,ViewModel是如何恢复数据的相关的知识,希望对你有一定的参考价值。
前言
当屏幕旋转或者切换系统语言时,Activity
生命周期从销毁再重建,但是ViewModel
里面的变量值不受到影响,说明ViewModel中的变量在屏幕旋转前进行了存储,在屏幕旋转后又进行了恢复。
里面的原理是怎么实现的呢?
一、获取ViewModel实例
// MainActivity.kt
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
这个代码拆分成2段来分析:ViewModelProvider(this)
和get(MainViewModel::class.java)
二、ViewModelProvider(this)
用于获取 ViewModelProvider
实例
// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
// ComponentActivity.java
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") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
// 先从 NonConfigurationInstances 获取
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
// 从缓存中恢复
mViewModelStore = nc.viewModelStore;
}
// 如果缓存里面没有,直接创建新的
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
代码很简单,可以看出,最后调用的是ComponentActivity
中的ensureViewModelStore()
方法,这个方法很重要。
这个方法涉及2个很重要的类:ViewModelStore
和 NonConfigurationInstances
。
ViewModelStore
ViewModelStore
从名字可以看出,是用来存储ViewModle
对象的,做一个缓存的作用,一般底层都是用Map或者List实现。来看下源码:
// ViewModelStore.java
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());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
代码很简单,可以看出ViewModelStore
里面就是一个HashMap
,用于缓存ViewModel
实例对象。
NonConfigurationInstances
// ComponentActivity$NonConfigurationInstances.java
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
这其实就是一个Java Bean类,里面存在2个字段,包过 viewModelStore
字段。
三、get(MainViewModel::class.java)
// ViewModelProvider.java
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);
}
// ViewModelProvider.java
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 {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
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打印:
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
Log.i("wutao--> ", "viewModel--> $viewModel" + " getViewModelStore--> ${getViewModelStore()}")
屏幕旋转后,mViewModelStore和viewModel对象地址确实是同一个。
四、mViewModelStore 的恢复
获取 mViewModelStore
代码如下:
// ComponentActivity.java
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
// 屏幕旋转在这里恢复
mViewModelStore = nc.viewModelStore;
}
// 屏幕旋转后,数据恢复不是new出来的对象
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获取的,那屏幕旋转前肯定也是在这里存储的。
搜索调用的地方:
从上图片可以看到是在第二处,代码如下:
// ComponentActivity.java
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
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()
中恢复。
// Activity.java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
mLastNonConfigurationInstances
赋值的地方:
// Activity.java
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
调用的:
// ActivityThread.java
Activity.NonConfigurationInstances lastNonConfigurationInstances;
/** Core implementation of activity launch. */
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搜索下,代码如下:
// ActivityThread.java
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中的数据不会恢复。
附上总体流程图:
以上是关于屏幕旋转导致Activity销毁重建,ViewModel是如何恢复数据的的主要内容,如果未能解决你的问题,请参考以下文章