ViewModel浅析
本篇文章不关注ViewModel是如何使用的,主要讨论的是ViewModel的原理以及它是如何创建的
ViewModel的创建
我们在创建ViewModel的时候,有多种方式,比如,我们可以直接调用它的构造方法
val model = MyViewModel()
我们也可以使用下面的方法来创建
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
注意,使用ViewModelProviders需要导入下面的依赖
implementation "android.arch.lifecycle:extensions:1.1.1"
说明:不推荐直接调用ViewModel的构造参数去创建一个ViewModel,因为ViewModel的主要目的是去保存数据,比如在我们的Activity翻转的时候,Activity的onDestroy方法会被调用,然后Activity的构造方法会被调用,然后Activity的onCreate方法会被调用,ViewModel的目的就是确保,销毁前的Activity与重新创建的Activity可以获取到同一个ViewModel,从而达到数据保存的目的,我们通过ViewModelProviders.of(..).get(..)的方式,就可以确保获取到的ViewModel是同一个ViewModel,但是,如果我们是直接调用ViewModel的构造参数去获取ViewModel,那么销毁前的Activity与重新创建的Activity获取到的不是同一个ViewModel。
最近,发现了有一种新的创建ViewModel的方式,就是调用ViewModelProvider的构造方法,创建一个ViewModelProvider,注意不是ViewModelProviders
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
然后调用ViewModelProvider的get方法,传入要构造的ViewModel的Class对象,去获取一个ViewModel
@NonNull
@MainThread
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构造参数中的ViewModelStore、Factory是什么呢?它与我们使用ViewModelProviders.of(..).get(..)的方式创建ViewModel有什么联系吗?我们后面会一一进行分析。
ViewModelProviders.of.get
我们先分析ViewModelProviders.of(..).get(..)是如何创建ViewModel的,这有利于我们后面的理解。
创建ViewModelProvider
我们使用下面的语句来创建ViewModel
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
首先查看ViewModelProviders.of方法,这个方法用于创建ViewModelProvider
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
调用了自己的重载方法,第二个参数传入了null
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
// 检查是否能获取到Application,如果可以则获取,不可以则抛出异常
Application application = checkApplication(activity);
// 若factory为null,那么就获取AndroidViewModelFactory,并赋值为factory
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
// 获取activity的ViewModelStore,然后创建ViewModelProvider,构造参数为
// ViewModelStore和factory
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
AndroidViewModelFactory是创建ViewModel的一个工厂,ViewModelStore用于存储ViewModel,getViewModelStore方法定义在ViewModelStoreOwner接口中,ComponentActivity实现了该接口
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
另外,Fragment类也实现了ViewModelStoreOwner接口,在Fragment中也可以调用getViewModelStore方法获取对应的ViewModelStore。
上面提到AndroidViewModelFactory,它的定义如下
public static class AndroidViewModelFactory extends
ViewModelProvider.NewInstanceFactory {
// AndroidViewModelFactory采取单例的方式进行定义
private static AndroidViewModelFactory sInstance;
@NonNull
public static AndroidViewModelFactory getInstance(@NonNull Application
application) {
if (sInstance == null) {
sInstance = new AndroidViewModelFactory(application);
}
return sInstance;
}
// AndroidViewModelFactory持有Application的引用
private Application mApplication;
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
@NonNull
@Override
// 创建ViewModel的方法
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
try {
return modelClass.getConstructor(Application.class)
.newInstance(mApplication);
} catch ...
}
return super.create(modelClass);
}
}
继承关系:AndroidViewModelFactory -> NewInstanceFactory -> Factory,它们都定义在ViewModelProvider里面,Factory接口定义了create方法,用于创建ViewModel。
我们上面调用了ViewModelProvider的构造方法,它里面其实就是将ViewModelStore和Factory保存起来了
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
总结一下,ViewModelStore就是Activity中的ViewModelStore,它用于存储ViewModel,由于我们没有主动指定Factory,所以这里Factory就是系统默认的AndroidViewModelFactory。
创建ViewModel
调用ViewModelProvider的get方法,就可以创建ViewModel
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
// 获取ViewModel的 包名.类名
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be
ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
调用了自己的重载方法get,传入两个参数,第一个参数是key,用于标识ViewModel,这里使用ViewModel的「包名+类名」来标识ViewModel,第二个参数是ViewModel对应的Class对象
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 在ViewModelStore中查找是否有key对应的ViewModel
ViewModel viewModel = mViewModelStore.get(key);
// 该方法类似于instanceof的语句,如果viewModel对应的类型,是modelClass对应的类型,或者
// 是modelClass的子类类型,那么该方法返回true,否则返回false。如果viewModel为null,该方
// 法也返回false
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 {
// 利用AndroidViewModelFactory去创建一个ViewModel
viewModel = (mFactory).create(modelClass);
}
// 将ViewModel存入ViewModelStore中
mViewModelStore.put(key, viewModel);
//noinspection unchecked
// 返回ViewModel
return (T) viewModel;
}
这里我们需要关注三点:
AndroidViewModelFactory如何创建ViewModel的ViewModelStore是如何缓存ViewModel的ViewModelStore是如何获取的
AndroidViewModelFactory创建VM
上面调用了AndroidViewModelFactory的create方法创建了一个ViewModel,它的create方法如下
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
// 如果AndroidViewModel是modelClass对应的类型的父类,该方法返回true,进入If
// 语句,否则该方法返回false
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch ...
}
// 调用父类的create方法
return super.create(modelClass);
}
其实AndroidViewModel就是内部包含了一个Application对象的ViewModel,一般我们使用ViewModel的时候也没有去继承AndroidViewModel,所以这里我们就认为它不会进入If语句,而是直接调用了父类的create方法。
AndroidViewModelFactory的父类是NewInstanceFactory,它的create方法如下
public static class NewInstanceFactory implements Factory {
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
我们可以看到这里调用了modelClass的newInstance方法,也就是modeClass对应的ViewModel需要有一个空参数的构造方法,否则就会构造失败,抛出异常。
所以,如果我们使用ViewModelProviders.of.get的方式构造ViewModel,那么只可以构造出无参数的ViewModel,如果我们的ViewModel的构造方法是有参数的,通过ViewModelProviders.of.get的方式将会构造失败,这时候我们就需要自定义Factory来构造ViewModel。
ViewModelStore的缓存
我们直接看下ViewModelStore的源码
public class ViewModelStore {
// 存储ViewModel的Map
private final HashMap<String, ViewModel> mMap = new HashMap<>();
// 存入ViewModel
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
// 如果有旧的ViewModel与key对应,就调用旧的ViewModel的onCleared方法
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
// 根据key获取ViewModel,若key没有对应的ViewModel,该方法会返回null
final ViewModel get(String key) {
return mMap.get(key);
}
// 返回key的集合
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
// 调用所有ViewModel的clear方法,通知它们可以销毁了,并且清理干净mMap
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
ViewModelStore的获取
ViewModelStore是在创建ViewModelProvider的时候获取的
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
这里调用了activity的getViewModelStore方法获取ViewModelStore,该方法如下
@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.");
}
// 判断mViewModelStore是否为null
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
// 查看是否可以从nc恢复ViewModelStore
mViewModelStore = nc.viewModelStore;
}
// 若没有恢复成功,就新创建一个ViewModelStore
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
// 返回ViewModelStore
return mViewModelStore;
}
这里我们分为三种场景分别讨论ViewModelStore的获取
Activity是新创建的:这时候mViewModelStore和nc均为null,那么就新构造一个ViewModelStore并返回。- 在场景1的基础上,
Activity已经创建了一个mViewModelStore,但是Activity由于配置更改,例如横竖屏的切换,导致Activity销毁又重建:这时候mViewModelStore为null,但是nc不为null,并且从nc当中获取到的viewModelStore也不为null,这时候重建的Activity和销毁前的Activity拿到的ViewModelStore是同一个,然后就可以通过同一个ViewModelStore,去获取之前的ViewModel,以达到配置更改但是数据得到保存的目的。 - 在场景1的基础上,
Activity已经创建了一个mViewModelStore,这时候我主动去销毁该Activity,然后又重新启动该Activity:该场景和场景1一样,即mViewModelStore和nc均为null,那么就新构造一个ViewModelStore并返回。
小结
到了这里,我们就明白了
- 通过
ViewModelProviders.of.get的方式是如何做到,在横竖屏切换时,销毁前的Activity与重新创建的Activity可以获取到同一个ViewModel。 ViewModelProviders.of.get默认只可以构造没有参数的ViewModel,如果ViewModel带有参数,我们就需要自定义构造ViewModel的Factory。
自定义Factory
例如现在我们的ViewModel构造方法是带有参数的
class MyViewModel(val arg: Int) : ViewModel() {
...
}
那么我们就需要自定义一个Factory,在它的create方法中,调用MyViewModel的带有参数的构造方法,创建MyViewModel并返回
class VMFactory(val arg: Int) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyViewModel(arg) as T
}
}
接着在Activity当中,我们只需要主动地去构造ViewModelProvider,传入我们自定义的Factory,然后调用ViewModelProvider的get方法,即可获取MyViewModel的实例
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_model)
val model = ViewModelProvider(viewModelStore,
VMFactory(10)).get(MyViewModel::class.java)
...
}
}
这里构造ViewModelProvider是使用了和ViewModelProviders.of.get一样的构造方法
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
但是传入一个ViewModelStore总归是有点别扭,其实ViewModelProvider还有另外一个构造方法
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
这个方法中,我们只需要传入一个ViewModelStoreOwner,然后它就会获取owner的ViewModelStore,然后自动调用上面的含ViewModelStore,Factory参数的构造方法。
ComponentActivity和Fragment都实现了ViewModelStoreOwner接口,可以调用getViewModelStore方法,获取ViewModelStore。

浙公网安备 33010602011771号