The ViewModel class is designed to store and manage interface related data in a life-cycle way. The ViewModel class allows the data to remain after configuration changes such as screen rotation.
Today, let’s explore how to retain data after configuration changes.
First, let’s look at how to createViewModel
example:
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
}
}
establishViewModel
example
// 1
val viewModelProvider = ViewModelProvider(this, CustomFactory())
// 2
val viewModel: MainViewModel= viewModelProvider.get(MainViewModel::class.java)
//Or use KTX to create
//val model : MainViewModel by viewModels { CustomFactory() }
ViewModelProvider
Source code
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
Source code
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
Look at what the above code does:
-
First, create an instance of viewmodelprovider,
ViewModelProvider
The first argument to the constructor isViewModelStoreOwner
, then why can it be passed inActivity
Examples, becauseComponentActivity
RealizedViewModelStoreOwner
This interface; -
adopt
get(@NonNull Class<T> modelClass)
Method getsViewModel
Examples of; -
adopt
owner.getViewModelStore()
GetViewModelStore
, that isComponentActivity
ofgetViewModelStore()
method.
Then let’s take a lookViewModelProvider#get()
method:
@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;
}
}
As you can see from the code above,ViewModelStore
Hold one inHashMap
, ifViewModelStore
Cached inViewModel
Instance, return directly. Otherwise, create a new instance and save it toViewModelStore
Yes.
Next, let’s take a lookComponentActivity#getViewModelStore()
method:
@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();
}
}
}
As can be seen from the above code:
-
First from
Activity
ofgetLastNonConfigurationInstance()
Get oneNonConfigurationInstances
examplenc
; -
If this
nc
If it is not empty, it willnc
Viewmodelstore assigned toComponentActivity
ofmViewModelStore
Field; -
If
mViewModelStore
Or fornull
, create a new onemViewModelStore
Object;
ObviouslyActivity
ofgetLastNonConfigurationInstance()
Is cacheViewModelStore
The core of.
@Nullable
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
This method returnsActivity
ofonRetainNonConfigurationInstance()
Hold the object, butActivity
This method returnsnull
。
According to the source code comments,Once the configuration changes and destroys the activity, the system will create a new activity instance for the new configuration. This method will be called by the Android system。
We can return any object in this method, including the activity instance itself, which can be called later in the new activity instancegetLastNonfigurationInstance()
To retrieve it.
Continue to track the source code and find it inActivity
ofretainNonConfigurationInstances()
Called inonRetainNonConfigurationInstance()
method.
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;
}
As can be seen from the above code:
-
First call
onRetainNonConfigurationInstance()
Get an object, which can be any object we want to save when the configuration is changed; -
As long as these objects are all
null
, no data will be returned; -
Create a static inner class
NonConfigurationInstances
Examples ofnci
And then assign it tonci
ofactivity
Field;
So who didActivity
ofonRetainNonConfigurationInstance()
Well, by tracking the code, it is found thatComponentActivity
Override this method:
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;
}
}
To sum up:
- If we want to save any object when the system configuration changes and restore this object when the system creates a new activity instance for the new configuration, we only need to rewrite the activity
onRetainNonConfigurationInstance()
Method is enough. - According to Article 1, when a configuration change destroys an activity,
onRetainNonConfigurationInstance()
SavedViewModelStore
, andViewModel
Pass againViewModelStore
To access, so whenActivity
When you rebuild, you can get the previous informationViewModel
。
How is the system retained and restoredNonConfigurationInstances
of
As we know from the previous chapter,Activity
ofonRetainNonConfigurationInstance()
It is called by the system. When did the system call it?
According to experience,ActivityThread
be responsible forActivity
Scheduling and execution, then we’ll goActivityThread
Search inActivity
ofretainNonConfigurationInstances()
method.
Tip: Android framework source code generally has the following relationship links:
schedule
(arrangement) – >handler
(treatment) – >perform
(execution) – >on
(in progress)
ActivityThread
ofperformDestroyActivity()
method
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;
}
WhenActivity
Called when destroyed or rebuilt due to configuration changesActivityThread
ofhandleRelaunchActivityInner()
method:
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);
}
As can be seen from the above code:
- Execute first
handleDestroyActivity
, and willgetNonConfigInstance
Set astrue
, so you canActivity
ofonRetainNonConfigurationInstance()
Retained data, saving toActivityClientRecord
Medium; - Then execute
handleLaunchActivity()
, rebuildActivity
And restore the data.
ActivityThread
ofperformLaunchActivity()
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;
}
As can be seen from the above code:
- establish
Activity
Examples of; -
attach
Data will be destroyed due to configuration changesActivity
Retained toActivityClientRecord
Held inlastNonConfigurationInstances
Assign to newActivity
; - Then you can call
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 情况一样吗
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
中的数据。