ViewModel用于保存数据的值和状态。
1.一般情况下,屏幕发生选择后,Activity会销毁重新创建,这时定义在Activity中的变量的值就会丢失,重新变为初始状态。
1)可以在Activity标签下配置android:configChanges="orientation|screenSize",禁止屏幕旋转后Activity重新创建。
2)也可以在onSaveInstanceState和onRestoreInstanceState进行数据的缓存和恢复
3)另外一种就是将要介绍的ViewModel,可以用于保存数据,在发生屏幕旋转,Activity重新创建后,保存的数据不会丢失。
2.简单使用
1)创建ViewModel子类,用于保存数据
public class MyViewModel extends ViewModel {
public int age ;
//数据回收时调用
@Override
protected void onCleared() {
super.onCleared();
}
}
2)Activity中使用,通过反射方式拿到MyViewModel对象,里面的定义的age就不会随着屏幕旋转导致的Activity销毁创建而发生数据的变化。
public class ViewModelActivity extends AppCompatActivity {
private MyViewModel viewModel;
private TextView ageTxt;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model);
ageTxt = findViewById(R.id.age);
//通过反射方式获取MyViewModel实例
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
ageTxt.setText(String.valueOf(viewModel.age));
findViewById(R.id.set_value).setOnClickListener(v -> {
ageTxt.setText(String.valueOf(++viewModel.age));
});
}
}
3.ViewModel能够进行数据恢复的原理,源码分析。
//通过反射方式获取MyViewModel实例
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
先看ViewModelProvider构造函数。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
1)owner.getViewModelStore()。他的实现是定义在了ComponentActivity
@NonNull
@Override
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;
}
ensureViewModelStore方法
里面有以行注释,Restore the ViewModelStore from NonConfigurationInstances
从nc中恢复mViewModelStore 。如果为null就new 一个,如果不为null返回的就是这个。
系统就是通过这个对象,进行数据恢复的。
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
首先看ViewModelStore是啥?
ViewModelStore中有一个hashMap,
map中存放了ViewModel实例,而我们定义的数据就存放在ViewModel中。
public class ViewModelStore {
private final HashMap 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 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();
}
}
再看这行代码 NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
实现定义在了Activity中。
如果mLastNonConfigurationInstances不是null
就从 mLastNonConfigurationInstances.activity得到一个Object,赋值给FragmentActivity.NonConfigurationInstances
然后从这里面得到了ViewModelStore。那mLastNonConfigurationInstances又是啥,是在哪里赋值的呢?
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
mLastNonConfigurationInstances是在Activity#Attached赋值的。
mLastNonConfigurationInstances是在Activity#Attached赋值的。
final void attach(Context context, ActivityThread aThread,...,
NonConfigurationInstances lastNonConfigurationInstances,
...) {
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
mLastNonConfigurationInstances是在attach方法中传进来的
attach是在performLaunchActivity方法中调用的。
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.voiceInteractor);
}
r.lastNonConfigurationInstances就保存在了ActivityClientRecord中
static final class ActivityClientRecord {
Activity.NonConfigurationInstances lastNonConfigurationInstances;
}
所以Activity在重建时,就从ViewModel中恢复了自定义的数据。
那么在Activity重建之前是如何保存数据的呢?
要清楚屏幕旋转后Activity重建的方法调用流程。
Activity正常开启时调用的方法是ActivityThread中的handleLaunchActivity()
旋转屏幕,Activity重建调用的是handleRelaunchActivity方法,
一个是Launch一个是Relaunch
callCallActivityOnSaveInstanceState会调用Activity的数据保存的方法onSaveInstanceState
我们可以通过onSaveInstanceState方法中存储要恢复的数据
在handleDestroyActivity方法中会调用performDestroyActivity方法
private void handleRelaunchActivity(ActivityClientRecord tmp) {
if (!r.paused) {
performPauseActivity(r.token, false, r.isPreHoneycomb());
}
if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
//在这个方法中调用Activity的数据恢复的方法onSaveInstanceState
callCallActivityOnSaveInstanceState(r);
}
handleDestroyActivity(r.token, false, configChanges, true);
//中间代码省略
..............
//执行完上面的操作后,会调用handleLaunchActivity,开始调用Activity创建时的声明周期函数
handleLaunchActivity(r, currentIntent);
}
在performDestroyActivity中除了执行Activity正常的销毁的声明周期函数,
还给 r.lastNonConfigurationInstances进行了赋值,在这里把ViewModel中的数据存储起来的。
private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
if (!r.paused) {
mInstrumentation.callActivityOnPause(r.activity);
}
if (!r.stopped) {
r.activity.performStop();
}
if (getNonConfigInstance) {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
}
mInstrumentation.callActivityOnDestroy(r.activity);
}
看r.activity.retainNonConfigurationInstances(),retain在英文中就有保持保留的意思。
在Activity中retainNonConfigurationInstances方法
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
.....
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
......
return nci;
}
onRetainNonConfigurationInstance方法是在FragmentActivity中定义的
在这个方法中返回了FragmentActivity.NonConfigurationInstances对象,并把Fragment中的mViewModelStore保存了起来
public final Object onRetainNonConfigurationInstance() {
Object custom = this.onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = this.mFragments.retainNestedNonConfig();
if (fragments == null && this.mViewModelStore == null && custom == null) {
return null;
} else {
FragmentActivity.NonConfigurationInstances nci = new FragmentActivity.NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = this.mViewModelStore;
nci.fragments = fragments;
return nci;
}
}
NonConfigurationInstances实例中持有一个activity对象,这个activity指向 FragmentActivity.NonConfigurationInstances 的实例。
而ActivityClientRecord中的lastNonConfigurationInstances 就是NonConfigurationInstances的实例。
再回头看看恢复的代码
mLastNonConfigurationInstances就是ActivityClientRecord中的lastNonConfigurationInstances
final void attach(Context context, ActivityThread aThread,...,
NonConfigurationInstances lastNonConfigurationInstances,
...) {
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
FragmentActivity.NonConfigurationInstances nc =
(FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null) {
this.mViewModelStore = nc.viewModelStore;
}
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
getLastNonConfigurationInstance返回的是NonConfigurationInstances的实例,如果不为空
从这里拿到activity赋值给FragmentActivity.NonConfigurationInstances,然后从这里面拿到保存的
nc.viewModelStore赋值给FragmentActivity的mViewModelStore。
而ViewModelStore保存了我们自定义的ViewModel实例和数据。
这样ViewModel就完成了数据的保存和恢复。
那有人可能会有这个疑问了,旋转屏幕会销毁Activity并执行Activity创建的声明周期函数,这样就能恢复数据。
按下back键或者finish,还能恢复之前的数据吗,肯定是不能的。为什么呢,同样都会执行重建的声明周期。
问题的关键在于这里performDestroyActivity方法中getNonConfigInstance这个boolean值是true还是false,
true就保存数据,false就不保存数据。
private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
if (getNonConfigInstance) {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
}
}
}
Activity重建会调用handleRelaunchActivity在这传进来的值是true
private void handleRelaunchActivity(ActivityClientRecord tmp) {
handleDestroyActivity(r.token, false, configChanges, true);
}
而正常finish也会调用handleDestroyActivity方法,但是这时传进来的值是false。
ActivityThread handler中
case DESTROY_ACTIVITY:
handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
msg.arg2, false);
break;