1、ViewModel的获取
获取一个viewModel对象最基本的方法如下:
val viewModel = ViewModelProvider(owner).get(ActivityViewModel::class.java)
先看后面get方法:
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
将传入class的canonicalName和DEFAULT_KEY拼接成一个字符串,然后传入get的重载方法中:
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel!!)
return viewModel as T
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
可以看到上面拼接的字符串是作为一个key,在store中获取该key对应的值(即viewModel对象)。如果获取到的值是传入的class的实例,就返回该对象,否则就通过factory创建一个对象返回,并将其存入store中。
2、ViewModel的存储
上面说了viewModel先从store中获取,说明store中存储着viewModel对象。那这个store是什么?这回再来看ViewModelProvider(owner):
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
调用了重载构造方法:
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {...}
从这里可以看到store类型是ViewModelStore,是从ViewModelStoreOwner中得到的。
2.1. ViewModelStore
顾名思义它的作用是存储viewModel,这个类实现非常简单:
open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun put(key: String, viewModel: ViewModel) {
val oldViewModel = map.put(key, viewModel)
oldViewModel?.onCleared()
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
operator fun get(key: String): ViewModel? {
return map[key]
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun keys(): Set<String> {
return HashSet(map.keys)
}
//清理所存储的所有viewModel
fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}
里面仅有一个map字段用来存储viewModel,key就是上面提到的拼接的字符串,value就是viewModel对象。所以在不同的地方如果使用同一个ViewModelStore对象获取相同类型的ViewModel,那么得到的会是同一个对象。这也就是横竖屏切换viewModel能保存数据以及fragment与activity共享viewModel的原理。这个后面再说。
2.2. ViewModelStoreOwner
ViewModelStoreOwner也非常简单,是一个接口:
A scope that owns ViewModelStore.
interface ViewModelStoreOwner {
val viewModelStore: ViewModelStore
}
很明显它的作用是返回ViewModelStore。ComponentActivity和Fragment类都实现了这个接口,所以在activity和fragment中创建ViewModelProvider可以直接传this。
看下它在ComponentActivity和Fragment类中的实现:
- ComponentActivity中
private ViewModelStore mViewModelStore;
...
@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(); //确保mViewModelStore被实例化
return mViewModelStore;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
//先看是不是activity异常销毁重建后需要恢复viewModel数据的情况,这个最后再分析
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
//如果不是需要恢复数据,就new出一个ViewModelStore对象。
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
ViewModelStore是直接放在Activity中的,正常情况下每个activity实例都有自己的ViewModelStore对象。
- Fragment中
Fragment情况稍微复杂一点,ViewModelStore不是直接放在Fragment中,而是从FragmentManager中获取:
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
...
return mFragmentManager.getViewModelStore(this);
}
可以看到调用了FragmentManager的getViewModelStore方法,并把自身作为参数传入,继续跟进:
private FragmentManagerViewModel mNonConfig;
...
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
在FragmentManager中又是直接调用了mNonConfig的getViewModelStore方法,这个mNonConfig是FragmentManager中的一个变量,类型是FragmentManagerViewModel(FragmentManagerViewModel本身也是一个ViewModel,它也是被存储在一个ViewModelStore中的,这个稍后分析),继续跟进:
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
...
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
可以看到很简单,FragmentManagerViewModel里有一个map,用来存储所有Fragment实例(用mWho字段代替实例,每个实例的mWho是唯一的)和其对应的ViewModelStore对象。获取某个Fragment的ViewModelStore就是从map里获取,如果没有就new一个出来并存入map中。所以也可以说每个Fragment实例都有自己的ViewModelStore对象。
2.3. FragmentManagerViewModel
刚才提到了FragmentManagerViewModel本身也是一个ViewModel,它也是被存储在一个ViewModelStore中的,那它是何时被创建的?存储在哪个ViewModelStore中?
在FragmentManager中搜索mNonConfig的赋值代码,在attachController方法中,结果如下:
void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
mHost = host;
mContainer = container;
mParent = parent;
...
// Get the FragmentManagerViewModel
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
...
}
可以看到mNonConfig的赋值与parent和host参数的值有关。这里为了不打断思路直接说结果,一般情况下parent为null,host实现了ViewModelStoreOwner接口,并且返回的ViewModelStore就是
FragmentManager所关联的Activity的ViewModelStore。所以走的是mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore)。看一下FragmentManagerViewModel.getInstance(viewModelStore)方法:
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}
很简单了,就是使用ViewModelProvider来获取ViewModel,传入viewModelStore,也就是Activity的viewModelStore,所以FragmentManagerViewModel是存储在FragmentManager所关联的Activity的ViewModelStore中,可以说是与Activity的业务ViewModel处于并列地位。
- 下面来分析一下parent和host
这两个是attachController方法的参数,所以要看attachController方法调用处的传参。该方法不是在FragmentManager内部调用的,所以需要找在哪里用到了FragmentManager。我们一般是在Activity中使用getSupportFragmentManager方法获取FragmentManager,看一下这个方法:
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
...
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
可以看到FragmentManager不是直接放在Activity中的,是通过mFragments.getSupportFragmentManager方法获取的,这个mFragments是Activity的一个常量,类型是FragmentController,值是通过createController方法获取的,createController方法里面就是直接调用的构造方法。
FragmentActivity不会直接操作FragmentManager,是通过FragmentController来操作FragmentManager的,所以在FragmentActivity中没有FragmentManager。
再来看下mFragments.getSupportFragmentManager方法:
private final FragmentHostCallback<?> mHost;
@NonNull
public static FragmentController createController(@NonNull FragmentHostCallback<?> callbacks) {
return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
}
private FragmentController(FragmentHostCallback<?> callbacks) {
mHost = callbacks;
}
@NonNull
public FragmentManager getSupportFragmentManager() {
return mHost.mFragmentManager;
}
FragmentManager依然不是放在FragmentController中的,返回的是mHost中的字段:
public abstract class FragmentHostCallback<E> extends FragmentContainer {
...
final FragmentManager mFragmentManager = new FragmentManagerImpl();
....
}
所以 FragmentManager最终是放在FragmentHostCallback中的,并且每个Activity对象对应一个FragmentManager。
在FragmentActivity、FragmentController、FragmentHostCallback中搜索FragmentManager的attachController方法,发现是在FragmentController中调用的:
public void attachHost(@Nullable Fragment parent) {
mHost.mFragmentManager.attachController(
mHost, mHost /*container*/, parent);
}
可以看到attachController方法的host参数就是这里的mHost,而从上面的代码可知mHost是在构造方法中赋值的,所以它的值就是在FragmentActivity中获取FragmentController对象时传入的new HostCallbacks()。
看一下HostCallbacks类:
class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
OnConfigurationChangedProvider,
OnTrimMemoryProvider,
OnMultiWindowModeChangedProvider,
OnPictureInPictureModeChangedProvider,
ViewModelStoreOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
SavedStateRegistryOwner,
FragmentOnAttachListener,
MenuHost {
...
@NonNull
@Override
public ViewModelStore getViewModelStore() {
return FragmentActivity.this.getViewModelStore();
}
它是FragmentActivity中的一个内部类,继承自FragmentHostCallback,实现了ViewModelStoreOwner接口,并且getViewModelStore方法实现是直接返回的FragmentActivity的ViewModelStore。
host参数分析完了,再来看parent参数。parent参数是由FragmentController的attachHost方法传入的,FragmentController是在FragmentActivity中的,在FragmentActivity中搜一下:
private void init() {
...
addOnContextAvailableListener(context -> mFragments.attachHost(null /*parent*/));
}
可以看到是在init方法中调用了mFragments.attachHost方法,传入的parent参数是null。
至此,经过上面的分析,可以得出最终结论:
Fragment的ViewModel存储在它对应的ViewModelStore中,ViewModelStore存储在FragmentManagerViewModel中的map中,而FragmentManagerViewModel存储在FragmentManager所关联的Activity的ViewModelStore中。
用一张图来说明:
3、viewModel的清除
1. ComponentActivity中
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();
// isChangingConfigurations()方法返回值表示是否是配置变更销毁重建
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
}
在无参构造方法中给lifecycle注册了观察者,在生命周期到达onDestroy时,如果activity是由于配置变化(即isChangingConfigurations()方法返回true)导致的销毁,就不清除,因为这种情况销毁后会重建,还需要使用到销毁前viewModel中的数据;如果是正常的结束销毁,就清除掉ViewModelStore中存储的viewModel对象。
isChangingConfigurations()方法:该方法返回的是Activity的mChangingConfigurations变量的值,该值正常情况下是false,当配置变更导致销毁重建时就被赋值为true。这里提一句,可能在有些人的意识里ConfigurationChange就是屏幕旋转,这是不准确的,配置变化有很多种情况,比如语言地区变更、字体变更等等。只不过我们平时最常见到的配置变化就是屏幕旋转,其他的情况几乎遇不到。但这并不能把ConfigurationChange和屏幕旋转划等号。另外Activity有一个
recreate方法,这个方法的作用就是销毁并重建,与配置发生变化时销毁重建的流程是一样的,所以调用这个方法导致activity销毁时也不会清理viewModelStore。
2. Fragment中
从上面的分析我们知道,Fragment的ViewModel最终实际也是存在activity的ViewModelStore中,所以在正常情况下,如果activity销毁了,会清理ViewModelStore,则依附于该activity的Fragment的ViewModel自然也被清理了。
4、Activity销毁重建时ViewModel不销毁原理
原理其实并不深奥,先用白话说一下根本原理:
ActivityThread先拿到销毁重建前的Activity实例的ViewModelStore对象,然后将其传递给重建后的Activity实例,这样重建前后的Activity的ViewModelStore是同一个对象,那里面的ViewModel自然也是相同的了。
下面结合源码分析一下。上面的小节中提到过了ViewModelStore初始化的代码:
void ensureViewModelStore() {
if (mViewModelStore == null) {
//先通过getLastNonConfigurationInstance方法获取NonConfigurationInstances,如果不为null,
//说明是配置变更销毁重建,就从NonConfigurationInstances中获取ViewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
//如果不是需要恢复数据,就new出一个ViewModelStore对象。
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
这个NonConfigurationInstances是ComponentActivity中的静态内部类(后续简称C_NCI),专门用于配置变更销毁重建时保存数据用。里面只有两个变量:
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
销毁前的activity实例的viewModelStore对象就是保存在这里,然后传递给重建后的activity实例。看下getLastNonConfigurationInstance方法,这个方法位于Activity中:
NonConfigurationInstances mLastNonConfigurationInstances;
...
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
返回的是mLastNonConfigurationInstances.activity。可以发现mLastNonConfigurationInstances的类型也是NonConfigurationInstances。但是要注意这个NonConfigurationInstances是Activity类的内部类(后续简称A_NCI),和刚才上面提到的C_NCI是不同的。看下A_NCI:
static final class NonConfigurationInstances {
Object activity;
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}
可以看到有个名为'activity'的变量(这个**命名真绝了),类型是Object。从上面的代码可知这个变量的值就是销毁前的acticity实例的C_NCI对象。那么关键就是找到这个变量的值是如何来的。
4.1.获取销毁前的activity实例的A_NCI对象
搜索一下mLastNonConfigurationInstances变量赋值的代码:
final void attach(...,NonConfigurationInstances lastNonConfigurationInstances,...) {
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
}
可以看到是在attach方法中。attach方法我们知道是由系统的ActivityThread调用的,现在目光要进入ActivityThread中。
当一个activity实例需要销毁重建时,经过系统的一系列处理,会走到ActivityThread的handleRelaunchActivity方法:
public void handleRelaunchActivity(ActivityClientRecord tmp,
PendingTransactionActions pendingActions) {
//这里mActivities是一个HashMap,存储了当前存活的所有activity实例
ActivityClientRecord r = mActivities.get(tmp.token);
...
r.activity.mChangingConfigurations = true;
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions,tmp.startsNotResumed,tmp.overrideConfig,"handleRelaunchActivity");
...
}
ActivityClientRecord可以理解为Activity的容器类,里面除了存放activity实例,还有一些其他数据,其中就包括我们要找的A_NCI。每一个activity实例都会对应一个ActivityClientRecord实例。
参数tmp就是要销毁重建的activity实例对应的ActivityClientRecord,从mActivities中根据tmp的token获取到实际的activity实例,将mChangingConfigurations变量置为true(前面提到的isChangingConfigurations()方法返回的值就是在这里被置为true的),然后调用handleRelaunchActivityInner方法,将r传入(注意这个r,它是销毁前的activity实例,它后续会被传入到许多方法中)。点进handleRelaunchActivityInner方法:
private void handleRelaunchActivityInner(ActivityClientRecord r,...) {
...
handleDestroyActivity(r, false, configChanges, true, reason); //注意这里第四个参数传入了true
...
handleLaunchActivity(r, pendingActions, customIntent);
}
可以看到先后调用了两个方法,从这里我们也可以得知Activity销毁重建过程的生命周期是先执行旧activity实例的onDestroy,再执行新activity实例的onCreate。点进handleDestroyActivity方法:
public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
...
}
调用了performDestroyActivity方法,继续跟进:
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
...
if (getNonConfigInstance) { //从上面可知这个值传入的是true
try {
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to retain activity "
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
}
try {
...
mInstrumentation.callActivityOnDestroy(r.activity);
...
} catch (SuperNotCalledException e) {
throw e;
}
...
可以看到在这里将r中的lastNonConfigurationInstances变量赋值为当前activity(当前activity就是销毁前的实例)的retainNonConfigurationInstances方法返回的值。这个方法作用就是销毁重建时保存数据用。看下这个方法:
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
...
return nci;
}
注意这个方法是Activity类中的方法,返回的是A_NCI。在这个方法里new了一个A_NCI对象,将'activity'变量赋值为onRetainNonConfigurationInstance方法返回的值,然后将A_NCI对象返回。再看onRetainNonConfigurationInstance方法:
public Object onRetainNonConfigurationInstance() {
return null;
}
发现直接返回的null。WTF??莫慌,这个方法在子类ComponentActivity中重写了。
另外之所以这个方法返回值(也即A_NCI中的activity变量)类型是Object,是为了兼容不同版本的实现,在不同版本的JDK或者不同的子类中可能实现保存数据功能的类型是不同的。
看下ComponentActivity中的这个方法:
public final Object onRetainNonConfigurationInstance() {
...
ViewModelStore viewModelStore = mViewModelStore;
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.viewModelStore = viewModelStore;
return nci;
}
可以看到返回值类型是C_NCI,new了一个C_NCI对象返回了,并且将C_NCI对象中的viewModelStore变量赋值为当前activity实例的viewModelStore。至此ActivityThread就拿到了销毁前activity实例的A_NCI对象,A_NCI对象中存有C_NCI对象,C_NCI对象中存有当前activity实例的viewModelStore。
4.2. 将销毁前的activity实例的A_NCI对象传递给重建后的activity实例
上一小节中分析了handleRelaunchActivityInner方法中的handleDestroyActivity方法,ActivityThread拿到了销毁前activity实例的A_NCI对象,下面来看如何传递给重建后的activity实例。看下handleLaunchActivity方法:
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
...
final Activity a = performLaunchActivity(r, customIntent);
...
}
调用了performLaunchActivity方法,跟进:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
if (activity != null) {
...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
r.assistToken, r.shareableActivityToken);
}
关键就在这里,使用mInstrumentation.newActivity方法创建了一个新的activity实例,然后调用其attach方法,将r.lastNonConfigurationInstances(即销毁前的activity实例的A_NCI对象)传入,如此一来新activity实例中mLastNonConfigurationInstances变量的值就是旧activity实例的A_NCI对象,因此销毁重建时从新activity实例的C_NCI对象中获取到的viewModelStore就是旧activity实例的viewModelStore对象。至此activity销毁重建viewModel能保存数据的原理分析完毕。
5、总结
-
viewModel对象是存储在ViewModelStore的map容器中,而每个Activity或Fragment(以下统称为owner)实例都有自己的ViewModelStore对象。每个owner实例可以有多个类型的ViewModel。
-
Activity的ViewModelStore对象直接就是由Activity持有;Fragment的ViewModelStore对象是由FragmentManagerViewModel中的map持有,而FragmentManagerViewModel本身也是一个ViewModel,它存储在和它关联的Activity的ViewModelStore中,也就是说FragmentManagerViewModel与和它关联的Activity的业务ViewModel处于并列地位。
-
在不同的owner实例(
注意是不同的实例,而不是不同的类型)中使用ViewModelProvider获取相同类型的ViewModel,得到的是不同的对象,因为不同的owner实例对应着不同的ViewModelStore对象。 -
要想Fragment共享Activity的ViewModel对象,就需要在Fragment中new ViewModelProvider时传入getActivity(),即它的宿主activity,这样ViewModelProvider拿到的ViewModelStore就是activity的store,获取到的viewModel就和activity的viewModel是同一个对象;同样如果要多个Fragment共享同一个ViewModel对象,也需要用getActivity()获取其宿主activity的ViewModelStore,这样获取到的才是同一个ViewModel对象。但是这里要注意,只有相同宿主activity下的多个Fragment可以共享同一个ViewModel对象,不同宿主activity下的Fragment用getActivity()获取到的ViewModel是不同的对象,所以不同宿主下的Fragment要想共享同一个ViewModel对象需要new ViewModelProvider时传入同一个自定义的ViewModelStore对象。