ViewModel

简述:fragmentcomponentActivity实现了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
// MainActivity.kt
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
// 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个很重要的类:ViewModelStoreNonConfigurationInstances

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
// 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

1
2
3
4
5
// ComponentActivity$NonConfigurationInstances.java
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
// 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打印:

1
2
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
Log.i("wutao--> ", "viewModel--> $viewModel" + " getViewModelStore--> ${getViewModelStore()}")

image.png

屏幕旋转后,mViewModelStore和viewModel对象地址确实是同一个。

四、mViewModelStore 的恢复

获取 mViewModelStore 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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获取的,那屏幕旋转前肯定也是在这里存储的。

搜索调用的地方:

image.png

从上图片可以看到是在第二处,代码如下:

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
// 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生命周期调用如下:

image.png

继续跟一下源码,寻找数据具体存储在哪里?

六、一探到底

Activity重启后数据的恢复

Activity 重建后,在 getLastNonConfigurationInstance() 中恢复。

1
2
3
4
5
// Activity.java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}

mLastNonConfigurationInstances 赋值的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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调用的:

1
2
3
4
5
6
7
8
9
10
11
12
// 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() 保存。

ActivityretainNonConfigurationInstances()方法中被调用。

retainNonConfigurationInstances()方法又是在哪调用的呢?肯定也跟ActivityThread有关,在ActivityThread搜索下,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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() 方法并把数据保存到了ActivityClientRecordlastNonConfigurationInstances中。

七、特例-系统杀后台

由于上文已经做过实验了,我这里直接贴上实验的打印结果 第一张图是模拟杀后台的生命周期打印,第二张图是屏幕旋转。

image.png

image.png

可以看出:

系统杀后台,Activity不会走onDestory()onRetainCustomNonConfigurationInstance()方法。 可以说杀掉后台,Activity销毁的生命周期都不会走,只有App再回到前台时,才会走Activity重建生命周期。

因为没有执行onRetainCustomNonConfigurationInstance()方法,Activity的数据也没有缓存下来,所以Activity重建也没有数据可以恢复。

下图是Activity中的ViewModel实例对象地址打印:

image.png

可以看出,adb模拟杀掉后台后,ViewModel地址值变了,是一个全新的地址。

如果想要系统内存不足,杀掉后台,App再次回到前台,之前的数据进行恢复,应该怎么处理?

请听下回分析。😁

八、总结

屏幕旋转前,Activity销毁时:

ComponentActivity调用onRetainNonConfigurationInstance()方法,将要销毁的ActivitymViewModelStore转化为NonConfigurationInstances对象,继续调用ActivityretainNonConfigurationInstances()方法,最终在ActivityThreadperformDestroyActivity()中将数据保存在ActivityClientRecord中。

Activity重建后:

Activity启动时,ActivityThread调用performLaunchActivity()方法,将存储在ActivityClientRecord中的lastNonConfigurationInstances通过Activityattach()方法传递到对应的Activity中,然后通过getLastNonConfigurationInstance()恢复mViewModelStore实例对象,最后根据对应的key拿到销毁前对应的ViewModel实例。

此外,当系统内存不足,系统将后台应用回收后,ViewModel中的数据不会恢复。

附上总体流程图:

image.png

Author

white crow

Posted on

2023-10-10

Updated on

2024-03-25

Licensed under