ViewModel框架介绍上是说被设计上用来在Activity或Fragment销毁、重建的时候保存它们的UI相关的数据。系统因为某种原因(屏幕旋转等)销毁,重新创建Activity的时候,存储在其中的任何临时性界面相关数据都会丢失,对于简单的数据,Activity 可以使用 onSaveInstanceState()
方法中的参数恢复其数据,但此方法仅适合可以序列化的少量数据,而不适合数量可能较大的数据。
在实践上,ViewModel还经常和LiveData配合使用,当Activity通过异步的方式从网络服务等地方获取数据的时候,可能会因为数据还未返回Activity就已经被退出了,从而造成内存泄漏和空指针异常,LiveData的优势就在于Activity已销毁的情况下会自动反注册,从而不存在上述风险。
从实现上来说,ViewModel框架很简单。因为它主要解决的是保存数据的问题,所以它的大部分组件都是用于构建通用的数据保存和访问的接口,以此保证不同的对象可以通过相同的方式访问到各自的数据。
ViewModel框架最核心的当然是ViewModel
类:
public abstract class ViewModel {
...
protected void onCleared()
@MainThread
final void clear() {
...
onCleared()
}
}
ViewModel
作为数据最终的保存类,没有定义任何数据存取的接口,这些都需要用户继承ViewModel
自己实现。ViewModel
提供了一个onCleared
回调,这个方法会在它的持有类ViewModelStore
清除数据的时候调用,用户可以在这个方法里面实现自己清理某些数据的逻辑。
ViewModelStore
就是一个简单的保存ViewModel
的数据结构:
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);
}
...
}
可以看到ViewModelStore
内部就是一个简单的HashMap
,就是起一个封装存取所有ViewModel
操作的作用,而最终,它会被ViewModelStoreOwner
持有:
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
ViewModelStoreOwner
是一个提供了获取ViewModelStore
对象方法的接口类,这个类就是实现ViewModel框架的功能的关键类、
ViewModel框架的理念很简单:怎么保证Activity重建之后还能获得原来的数据?把这个数据存在存活时间比Activity更长的对象就可以了!所以Activity或Fragment的管理类会实现或者持有ViewModelStoreOwner
的实现类,因为管理类肯定会比被管理的类存活时间更长。
除此之外,为了提供统一的访问接口,ViewModel框架定义了ViewModelProvider
类和ViewModelProviders
类:
public class ViewModelProvider {
public interface Factory {
<T extends ViewModel> T create(Class<T> modelClass);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory)
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
}
public class ViewModelProviders {
@MainThread
public static ViewModelProvider of(Fragment fragment,Factory factory)
@MainThread
public static ViewModelProvider of(FragmentActivity activity,Factory factory)
}
ViewModelProviders
提供了of
工具方法,通过传入的fragment或者activity,获取到对应的ViewModelStoreOwner
,然后生成一个新的ViewModelProvider
,ViewModelProvider
内部通过反射的方式,由用户传入的ViewModel
类的Class对象从ViewModelStore
中取出之前存入的对应的ViewModel。
ViewModelProvider
将反射生成ViewModel
的工厂方法声明成了Factory
接口,并且提供了ViewModel
构造参数为空的NewInstanceFactory
类和构造参数为Application
的AndroidViewModelFactory
类两个默认Factory
实现,当ViewModelProvider
的factory
参数为null(of方法有不需要Factory
的重载方法,我上面没有写出)时,就会调用这两个默认实现。
因为AndroidViewModelFactory
继承了NewInstanceFactory
并且两者的实现类似,所以我下面只介绍一下它的create
方法实现,可以看到它的create
方法,就是简单的判断传入的class类是不是AndroidViewModel
类(构造参数为Application
)来反射获取ViewModel对象,不是的话就通过父类来调用无参构造方法,如果都失败的话就会直接抛出运行时异常:
@Override
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);
}
老实说,看了ViewModel框架的实现之后,我有一点失望,因为它只能在同一个activity,或者同一个activity下的不同fragment之间共享数据(由ViewModel框架的实现原理决定),而没有拥有(我所期望的)在不同Activity乃至不同线程之间的共享数据的功能。但是我转念一想,Google不做,我可以自己动手,Android里面天然就有一个可以所有类都可以访问到的共享对象:Application!所以我让自己的Application继承ViewModelStoreOwner
,并且重载ViewModelProviders
提供对应的访问接口,这样就实现了跨Activity的数据共享:
public class MyApplication extends Application implements ViewModelStoreOwner {
private ViewModelStore mViewModelStore;
@Override
public void onCreate() {
super.onCreate();
mViewModelStore = new ViewModelStore();
}
@Override
public ViewModelStore getViewModelStore() {
return mViewModelStore;
}
}
public class VMProviders extends ViewModelProviders {
private static ViewModelStore checkApplicationAndGetStore(Application application) {
if (!(application instanceof MyApplication) || application != MyApplication.getApplication()) {
throw new IllegalArgumentException("Your Application is not true.");
} else {
return ((MyApplication) application).getViewModelStore();
}
}
public static ViewModelProvider of(Application application) {
return of(application, null);
}
public static ViewModelProvider of(Application application, Factory factory) {
ViewModelStore store = checkApplicationAndGetStore(application);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(store, factory);
}
}
通过上述方式,我实现了不同Activity之间共享数据,但是要实现跨线程共享数据就没这么简单了,首先,我需要保证ViewModel
是线程安全的,这就需要限制它的访问接口(原本是没有任何限制的),其次,我需要将ViewModelStore存储ViewModel
的Map修改成线程安全的,但我能修改的地方就只有Application
的ViewModelStore
,这样算来,我这种实现方案的缺陷其实很大。如果同学你有更好的方案,希望可以分享给我。