ViewModel介绍
ViewModel意为视图模型,其实Android平台上之所以会出现诸如MVP、MVVM之类的项目架构,就是因为在传统的开发模式下Activity的任务实在是太重了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,在一个小型项目中这样写或许没有什么问题,但是如果在大型项目中仍然使用这种写法的话,那么这个项目将会变得非常臃肿并且难以维护。所以ViewModel的核心功能就是为UI层提供数据模型
既然让ViewModel提供数据模型那么它的优点到底有哪些呢?
ViewModel的特点
- 首先当然是减轻activity的负担,项目更易维护
- 当发生屏幕旋转的时候activity会重建,一些简单的数据当然可以通过onSaveInstance去保存,可是此方法只适合序列化的数据,没序列化呢?或者说数据量太大呢?ViewModel的生命周期和activity不同,它能够保证在屏幕旋转的时候数据不丢失
- 传统的异步请求和UI层耦合比较严重,这就导致了UI层需要管理这些请求,确保界面销毁后清理这些调用以避免潜在的内存泄露,此项管理需要大量的维护工作,把请求放进ViewModel中能够避免UI层的内存泄露(因为ViewModel不持有UI层的引用),并且ViewModel已经提供了 onCleared 方法,为了避免资源浪费可以在该方法中对一些请求经行处理以便及时回收资源
源码分析
class MyViewModel :ViewModel(){
override fun onCleared() {
super.onCleared()
}
}
//step1
val provider = ViewModelProvider(this)
//step2
val viewModel = provider.get(MyViewModel::class.java)
一个ViewModel的使用就是如此简单,对于Activity和Fragment step2都是一样的先来看下step2:
private final ViewModelStore mViewModelStore;
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");
}
//通过getCanonicalName和DEFAULT_KEY构建出一个key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//通过key在ViewModelStore中去寻找对应的viewModel
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
if (viewModel != null) {
// TODO: log a warning.
}
}
//如果ViewModelStore中没有找到key对应的viewModel就create
//在create中其实就是通过反射去创建viewModel,感兴趣的可以去看下
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
//把key和构建出来的viewModel保存到ViewModelStore中再返回
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
get还是很简单的通过构建出来的key去 ViewModelStore 中寻找对应的viewModel,有就返回没有就通过反射去实例化一个viewModel,再保存到ViewModelStore中,最后再返回ViewModel。
那ViewModelStore到底是个什么呢?
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();
}
}
//ViewModel
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
//在ViewModel将被清除时调用
onCleared();
}
从上述代码可以看到ViewModelStore里面维护了一个HashMap去保存ViewModel,注意里面有个clear方法它会在Activity、Fragment销毁的时候调用,目的就是清理所有的ViewModel,ViewModel的clear中又会调用 onCleared。
现在主要理清以下几个问题:
- ViewModel为什么可以避免内存泄露
- Activity重建 ViewModel怎样保存数据
Fragment重建的情况和activity很相似这里就只讨论activity场景。
从上文对step2的分析我们可以看到ViewModel都是从 ViewModelStore 中取出来的,那么只要ViewModelStore不变,ViewModel就不会变,ViewModel所保存的数据当然也不会变,所以2、3点的核心肯定就在于 ViewModelStore。
避免内存泄露
其实在ViewModel的特点中已经分析了,它不会也不能持有UI层(Activity/Fragment)的引用,那当然不会造成内存泄露,并且ViewModel本身提供了onCleared方法供我们重写,在这里面可以去取消相关请求,清理相关资源。
Activity重建ViewModel如何保存数据
来看看step1:
//Activity中创建
val provider = ViewModelProvider(this)
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
ViewModelStore是ViewModel的存储器这点在上文已经证实了,ViewModelStoreOwner是一个接口顾名思义就是ViewModel存储器的拥有者,它只有一个方法getViewModelStore即获取ViewModelStore
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelProvider接收一个 ViewModelStoreOwner 参数,ComponentActivity 和Fragment 都实现了ViewModelStoreOwner接口,所以我们在Activity和Fragment中可以直接传this。所以这里的owner.getViewModelStore()就来到了ComponentActivity的getViewModelStore方法
public ViewModelStore getViewModelStore() {
//activity还没关联Application,即不能在onCreate之前去获取viewModel
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
//如果存储器是空,就先尝试 从lastNonConfigurationInstance中获取
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
//如果lastNonConfigurationInstance不存在,就new一个
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
目前从上文可以知道mViewModelStore要么从getLastNonConfigurationInstance中获取要么就直接new一个出来,先看看getLastNonConfigurationInstance中拿到的是什么东西:
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
getLastNonConfigurationInstance是Activity的方法如果mLastNonConfigurationInstances不为空就返回mLastNonConfigurationInstances.activity,看看这个mLastNonConfigurationInstances到底是什么它又在哪里赋值的:
static final class NonConfigurationInstances {
Object activity;
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}
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);
mFragments.attachHost(null /*parent*/);
mLastNonConfigurationInstances = lastNonConfigurationInstances;
}
可以看到NonConfigurationInstances是一个Activity的静态内部类,顾名思义就是非配置实例,mLastNonConfigurationInstances是在attach中被赋值的,这里就涉及到Activity的启动流程了,attach方法是ActivityThread的performLaunchActivity方法中调用,这里的lastNonConfigurationInstances是存在 ActivityClientRecord中的一个组件信息。
ActivityClientRecord保存在ActivityThread的mActivities中的:
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
现在我们已经知道Activity的mLastNonConfigurationInstances是在哪里赋值的,还有最后一个问题 mLastNonConfigurationInstances 的activity变量是一个Object类型它到底是什么又是在哪里赋值的呢?毕竟ViewModelStore就是通过activity变量取出的,在之前的代码我们也看到如果mLastNonConfigurationInstances获取的ViewModelStore为空的话会new一个ViewModelStore,那这个ViewModelStore是在什么时候和mLastNonConfigurationInstances关联上的呢?
在屏幕旋转的时候Activity会重建,同时会调用 onRetainNonConfigurationInstance方法:
public final Object onRetainNonConfigurationInstance() {
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,这个NonConfigurationInstances和Activity中的不同
//它是ComponentActivity的静态内部类,实例化后会把mViewModelStore赋值给它的变量
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
//ComponentActivity
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
再来看看 onRetainNonConfigurationInstance方法是在哪里调用的:
//这里来到了Activity中
NonConfigurationInstances retainNonConfigurationInstances() {
//ComponentActivity的onRetainNonConfigurationInstance是在这里调用的
//并且用Object来接收
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
//实例化了Activity的静态内部类,并把ComponentActivity的onRetainNonConfigurationInstance方法返回的值赋值给它的activity变量
//这就和之前的getLastNonConfigurationInstance方法联系上了
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}
Activity的retainNonConfigurationInstances是在ActivityThread的 performDestroyActivity 方法中调用:
//这个r就是 ActivityClientRecord,从mActivities中拿到的
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
这就给ActivityClientRecord的lastNonConfigurationInstances赋最新的值了,此时lastNonConfigurationInstances中变量activity就保存了ViewModelStore实例,继续往上追溯其实重建事件会来到 handleRelaunchActivityInner
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
handleDestroyActivity(r, false, configChanges, true, reason);
handleLaunchActivity(r, pendingActions, customIntent);
}
可以看到又会往下调用 handleLaunchActivity-->performLaunchActivity-->activity.attach,重建完成之后再去调用getViewModelStore()方法的话,就会直接从getLastNonConfigurationInstance中拿到ViewModelStore。
总体来说就是屏幕旋转的时候会从ActivityThread一层层向下调用直到来到 onRetainNonConfigurationInstance方法,它会把ViewModelStore封装到ComponentActivity中的 NonConfigurationInstances 中,Activity中的 NonConfigurationInstances 又会用变量activity去接收返回值,最后Activity的NonConfigurationInstances会被封装到 ActivityClientRecord。而Activity的reLaunch并不会销毁对应的ActivityClientRecord,下次仍然会复用ActivityClientRecord,进而复用保存的ViewModelStore。
onRetainNonConfigurationInstance 和 onSaveInstance 不一样,onSaveInstance是通过bundle保存到系统进程中、大小是有限制的一般是(1M)、App被杀死数据能恢复的;而 onRetainNonConfigurationInstance 这些数据是直接保存在App本身的进程中的,App被杀死数据不能恢复、大小限制就是App的可用内存。
ViewModel的销毁
public ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
看这段代码,这里给lifecycle增加了观察者去监听Activity的生命周期(对于lifecycle还不了解的同学可以看看这篇文章,Lifecycle源码分析 ),当屏幕发生旋转的时候生命周期事件会来到ON_DESTROY,但是这是因为配置信息发生改变所以isChangingConfigurations为true所以不会调用getViewModelStore().clear(),这样也说得通,ViewModel保存的只是数据,界面配置变化,并不需要数据重新加载,使用之前的也可以。只有activity真正销毁的时候比如调用finish,这个时候才会调用getViewModelStore().clear()去清理所有的ViewModel。
总结
现在这幅图就很清晰了,为什么说ViewModel的生命周期长于Activity、Fragment,就是因为在ON_DESTROY事件发生的时候,ViewModel会去判断是否是因配置信息的改变导致执行ON_DESTROY,这时不一定会执行clear方法。
总的来说通过ComponentActivity的onRetainNonConfigurationInstance,ActivityClientRecord会持有一条到ViewModelStore的引用链,Activity的reLaunch并不会销毁对应的ActivityClientRecord,下次仍然会复用ActivityClientRecord