ViewModel的使用和原理

Posted by alonealice on 2020-05-17

ViewModel 是一种用来存储和管理UI相关数据的类。同时在配合LiveData 情况下还支持在系统配置发生改变的时候自动对数据进行保存。它可以用来管理Activity和Fragment中的数据,也可以拿来处理Fragment与Fragment之间的通信等。

ViewModel的使用

添加依赖

1
2
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

使用方法

1
2
3
4
5
6
7
8
9
10
11
public class PersonModel extends ViewModel {
public MutableLiveData<Person> liveData = new MutableLiveData<>();
public PersonModel() {
liveData.postValue(new Person());
}
public void updateData() {
Person value = liveData.getValue();
value.name = "test";
liveData.setValue(value);
}
}
1
2
3
4
5
6
7
8
9
10
PersonModel personModel = new ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PersonModel.class);
personModel.liveData.observe(this, new Observer<Person>() {
@Override
public void onChanged(Person person) {
//更新界面
}
});

//更新数据
personModel.updateData();

当页面旋转等情况下重新调用onCreate时,ViewModel其实是没有重新创建的,还是之前那个ViewModel。ViewModel只有在activity被销毁之后才会销毁,因此ViewModel也不能持有activity引用。

源码分析

创建ViewModel的过程:

1
2
3
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
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);
//noinspection unchecked
return (T) viewModel;
}
1
2
3
4
5
6
7
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
return super.create(modelClass);
}

ViewModelProvider 的get方法中,首先会在mViewModelStore中根据class,获取对应的实例,如果没有,则调用AndroidViewModelFactory创建,并保存到mViewModelStore。

AndroidViewModelFactory专门用来实例化那种构造方法里面有参数的class,并且ViewModel里面可能是带Context的。它是通过newInstance(application)去实例化的.如果有带application参数则是这样实例化,如果没有带application参数的话,则还是会走newInstance()方法去构建实例。

然后看一下activity中的getViewModelStore方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
1
2
3
public Object getLastNonConfigurationInstance() {    
return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity :null;
}

Activity在横竖屏切换时悄悄保存了viewModelStore,放到了NonConfigurationInstances实例里面,横竖屏切换时保存了又恢复了回来,相当于ViewModel实例依然是原先的,也就因此避免了横竖屏切换时的数据丢失。

而ViewModelStore本身是就是一个简单存储数据的类。里面使用了hashmap用来存储数据:

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
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();
}
}

再和在activity的destory方法中会将mViewModelStore中的数据清除。