文章目录
ViewModel是如何在配置更改后继续留存数据的
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据,ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
那么我们今天就探索下,配置更改后,是如何继续留存数据的。
首先我们看下如何创建ViewModel实例:
class CustomFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when (modelClass) {
MainViewModel::class.java -> {
MainViewModel()
}
else -> throw IllegalArgumentException("Unknown class $modelClass")
} as T
}
}
创建ViewModel实例
// 1
val viewModelProvider = ViewModelProvider(this, CustomFactory())
// 2
val viewModel: MainViewModel= viewModelProvider.get(MainViewModel::class.java)
// 或者使用KTX来创建
//val model : MainViewModel by viewModels { CustomFactory() }
ViewModelProvider源码
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) {
// 3
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {
this.mFactory = factory;
this.mViewModelStore = store;
}
ViewModelStoreOwner源码
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
看看上面这段代码做了什么:
-
首先创建一个ViewModelProvider实例,
ViewModelProvider构造函数的第一个参数是ViewModelStoreOwner,那为什么可以传入Activity实例呢,是因为ComponentActivity实现了ViewModelStoreOwner这个接口; -
通过
get(@NonNull Class<T> modelClass)方法 获取到ViewModel的实例; -
通过
owner.getViewModelStore()获取到ViewModelStore,也就是ComponentActivity的getViewModelStore()方法。
然后我们看下ViewModelProvider#get()方法:
@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");
} else {
return this.get("androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName, modelClass);
}
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = this.mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (this.mFactory instanceof ViewModelProvider.OnRequeryFactory) {
((ViewModelProvider.OnRequeryFactory)this.mFactory).onRequery(viewModel);
}
return viewModel;
} else {
if (viewModel != null) {
}
if (this.mFactory instanceof ViewModelProvider.KeyedFactory) {
viewModel = ((ViewModelProvider.KeyedFactory)this.mFactory).create(key, modelClass);
} else {
viewModel = this.mFactory.create(modelClass);
}
this.mViewModelStore.put(key, viewModel);
return viewModel;
}
}
从在上面的代码中可以发现,ViewModelStore中持有一个HashMap,如果ViewModelStore中有缓存的ViewModel实例,就直接返回,否则创建新的实例并存入到ViewModelStore中。
接下来我们看下 ComponentActivity#getViewModelStore()方法:
@NonNull
public ViewModelStore getViewModelStore() {
if (this.getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
} else {
this.ensureViewModelStore();
return this.mViewModelStore;
}
}
void ensureViewModelStore() {
if (this.mViewModelStore == null) {
ComponentActivity.NonConfigurationInstances nc = (ComponentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null) {
this.mViewModelStore = nc.viewModelStore;
}
if (this.mViewModelStore == null) {
this.mViewModelStore = new ViewModelStore();
}
}
}
从以上代码中可以看出:
-
先是从
Activity的getLastNonConfigurationInstance()获取一个NonConfigurationInstances实例nc; -
如果这个
nc不等于空,就将nc的viewModelStore赋值给ComponentActivity的mViewModelStore字段; -
如果
mViewModelStore还是为null,就创建一个新的mViewModelStore对象;
很明显Activity的getLastNonConfigurationInstance()是缓存ViewModelStore的核心。
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
这个方法返回的是 Activity的onRetainNonConfigurationInstance()持有的对象,但是Activity的这个方法返回的是null。
根据源码注释可知,一旦配置更改销毁Activity,系统将为新配置创建一个新的Activity实例时,将由Android系统调用此方法。
我们可以在这个方法返回任何对象,包括Activity实例本身,稍后可以通过在新Activity实例中调用getLastNonfigurationInstance()来检索到它。
继续跟踪源码发现在Activity的retainNonConfigurationInstances()中调用了onRetainNonConfigurationInstance()方法。
NonConfigurationInstances retainNonConfigurationInstances() {
// 1
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
......
ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
// 2
if (activity == null && children == null && fragments == null && loaders == null
&& mVoiceInteractor == null) {
return null;
}
// 3
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
......
return nci;
}
从以上代码中可以看出:
-
先是调用
onRetainNonConfigurationInstance()获取一个对象,这个对象可以是任何我们想要在配置更改时要保存的对象; -
只要这些对象全部为
null,才会不返回数据; -
创建一个静态内部类
NonConfigurationInstances的实例nci,然后将赋值给nci的activity字段;
那么谁实现了 Activity的onRetainNonConfigurationInstance()呢,通过追踪代码发现是ComponentActivity重写了这个方法:
public final Object onRetainNonConfigurationInstance() {
Object custom = this.onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = this.mViewModelStore;
ComponentActivity.NonConfigurationInstances nci;
if (viewModelStore == null) {
nci = (ComponentActivity.NonConfigurationInstances) this.getLastNonConfigurationInstance();
if (nci != null) {
viewModelStore = nci.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
} else {
nci = new ComponentActivity.NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
}
总结一下:
- 如果我们想要在系统配置发生更改时保存任何对象,并在系统为新配置创建新Activity实例时恢复这个对象,那么我们只需要重写Activity的
onRetainNonConfigurationInstance()方法即可。 - 根据第一条,当配置更改销毁Activity时,
onRetainNonConfigurationInstance()保存了ViewModelStore,而ViewModel又通过ViewModelStore来存取,因此当Activity重建时,就能获取到之前的ViewModel。
系统是如何保留和恢复NonConfigurationInstances的
从上一个章节我们知道,Activity的onRetainNonConfigurationInstance()是由系统调用的,那么系统在什么时机调用的呢?
根据经验,ActivityThread负责Activity的调度和执行,那么我们就去ActivityThread中搜索下Activity的retainNonConfigurationInstances()方法。
提示:Android Framework源码一般会有如下的关系链路:
schedule(安排上) ————>handler(处理) ————>perform(执行) ————>on(在进行)
ActivityThread的performDestroyActivity()方法
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
Class<? extends Activity> activityClass = null;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
if (r != null) {
activityClass = r.activity.getClass();
r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
r.activity.mFinished = true;
}
performPauseActivityIfNeeded(r, "destroy");
if (!r.stopped) {
callActivityOnStop(r, false /* saveState */, "destroy");
}
// 1
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
...
}
}
}
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity);
if (!r.activity.mCalled) {
...
}
if (r.window != null) {
r.window.closeAllPanels();
}
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
r.setState(ON_DESTROY);
}
schedulePurgeIdler();
synchronized (mResourcesManager) {
mActivities.remove(token);
}
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
}
当Activity因配置更改被销毁重建时,会调用ActivityThread的handleRelaunchActivityInner()方法:
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
...
// 1
handleDestroyActivity(r.token, false, configChanges, true, reason);
...
// 2
handleLaunchActivity(r, pendingActions, customIntent);
}
从上面的代码可以看出:
- 首先执行
handleDestroyActivity,并且将getNonConfigInstance设为true, 这样就可以将Activity的onRetainNonConfigurationInstance()保留的数据,保存到ActivityClientRecord中; - 然后执行
handleLaunchActivity(),重建Activity,并将数据恢复。
ActivityThread的performLaunchActivity()
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
ContextImpl appContext = createBaseContextForActivity(r);
// 1
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
...
}
}
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
appContext.getResources().addLoaders(
app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
// 2
appContext.setOuterContext(activity);
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.configCallback,
r.assistToken);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
r.activity = activity;
...
}
r.setState(ON_CREATE);
synchronized (mResourcesManager) {
mActivities.put(r.token, r);
}
...
return activity;
}
从以上代码可以看出:
- 创建
Activity的实例; attach数据,将因配置更改销毁Activity留存到ActivityClientRecord中持有的lastNonConfigurationInstances赋值给新建的Activity;- 之后就可以通调用
Activity的getLastNonConfigurationInstance()检索到onRetainNonConfigurationInstance()方法保留的对象。
Activity的attach()方法
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*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
...
}
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}

上图是Framework源码留存和恢复NonConfigurationInstances的大致调用流程。
ViewModel 和 onSaveInstanceState 情况一样吗
onSaveInstanceState 方法的使用:
public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putSerializable("currentScreen", currentScreen)
outState.putParcelableArrayList("backstack", backstack)
}
onSaveInstanceState 方法在 Activity 可能被杀死之前被调用,以便当它在将来某个时间返回时可以恢复它的状态。例如,如果Activity B 在 Activity A 的前面被启动,在某个时间点 Activity A 被杀死回收资源,Activity A 将有机会通过这个方法保存其用户界面的当前状态,这样当用户返回到 Activity A 时,用户界面的状态可以通过 onCreate() 或 onRestoreInstanceState() 来恢复。
如果该方法被调用,该方法将发生在
android.os.Build.VERSION_CODESP以后平台版本的应用程序的onStop之后。对于以较早平台版本为目标的应用程序,该方法将出现在onStop之前,并且不能保证它将出现在onPause之前还是之后。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.leak_canary_leak_activity)
if (savedInstanceState == null) {
...
} else {
currentScreen = savedInstanceState.getSerializable("currentScreen") as Screen
backstack = savedInstanceState.getParcelableArrayList<Parcelable>(
"backstack"
) as ArrayList<BackstackFrame>
}
}
Activity 通常会在下面三种情况下被销毁:
-
从当前界面永久离开:用户导航至其他界面或直接关闭
Activity(通过点击返回按钮或执行的操作调用了 finish 方法)。对应Activity实例被永久关闭; -
Activity配置 (configuration) 被改变: 例如,旋转屏幕等操作,会使Activity需要立即重建; -
应用在后台时,其进程被系统杀死:这种情况发生在设备剩余运行内存不足,系统又亟须释放一些内存的时候。当进程在后台被杀死后,用户又返回该应用时,
Activity也需要被重建。
在后两种情况中,我们通常都希望重建 Activity。ViewModel 会帮我们处理第二种情况,因为在这种情况下 ViewModel 没有被销毁;而在第三种情况下, ViewModel 被销毁了。所以一旦出现了第三种情况,便需要在 Activity 的 onSaveInstanceState 相关回调中保存和恢复 ViewModel 中的数据。
本文探讨了在Android系统配置更改后,ViewModel是如何保存数据并在重建时恢复的机制。介绍了ViewModelStore与NonConfigurationInstances的作用,对比了ViewModel与onSaveInstanceState的区别。
696

被折叠的 条评论
为什么被折叠?



