[Framework] ViewModel 如何做到 Activity 重启不丢失数据
在 Android
屏幕旋转和手机配置改变都有可能造成 Activity
重启,在这个过程中旧的 Activity
数据就需要保存,然后需要把旧的数据传递给新的 Activity
,然后恢复重启前的状态,开发中处理这种重启的情况非常烦人😡,新手开发经常不会处理,然后导致状态丢失,严重的还会有一些崩溃,Android
官方自己也发现了这个问题然后提供了 ViewModel
,并告诉大家 Activity
中状态相关的数据都可以放在里面,并保证屏幕旋转等重启不会导致它的数据丢失。好了这就引出了我们今天的主题,我们来看看 ViewModel
是如何做到 Activity
重启数据不丢失的。
ViewModel 是如何保存数据的
我们一般像如下方式使用 ViewModel
:
kotlin
class MainActivity : ComponentActivity() {
val myViewModel: MyViewModel by lazy {
ViewModelProvider(this).get(MyViewModel::class.java)
}
// ...
}
首先创建一个 ViewModelProvider
实例,构造函数中需要传递一个 ViewModelStoreOwner
对象,好巧不巧,我们使用的 jitpack
中的 Activity
和 Fragment
他们都是 ViewModelStoreOwner
,后续我们也都用 Activity
作为分析源码的标准。然后通过 ViewModelProvider#get()
方法来获取我们想要的 ViewModel
实例。
首先看看 ViewModelProvider
的构造函数:
Kotlin
public constructor(
owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
owner.viewModelStore,
factory,
defaultCreationExtras(owner)
)
@JvmOverloads
constructor(
private val store: ViewModelStore,
private val factory: Factory,
private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)
朴实无华的构造函数,会直接通过我们的 ViewModelStoreOwner
来直接获取 ViewModelStore
,然后创建一个 defaultFactory
他就是我们用来生成 ViewModel
实例的仓库,后续我们再看看它。忽略掉 defaultCreationExtras
变量。
然后再看看 ViewModelProvider#get()
方法:
Kotlin
@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)
}
@Suppress("UNCHECKED_CAST")
@MainThread
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
的构建方式是 DEFAULT_KEY
+ ViewModel
的类名,直接从 ViewModelStore
中根据这个 key
去查找这个对象,同时还会判断它的类型,如果都没有问题会通知 OnRequeryFactory#onRequery()
方法,然后直接返回;如果从 ViewModelStore
中查询失败了,就通过 Factory
创建一个然后储存到 ViewModelStore
中,然后再返回。
我们再看看 AndroidViewModelFactory
是如何创建 ViewModel
的。
Kotlin
@Suppress("DocumentExceptions")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (application == null) {
throw UnsupportedOperationException(
"AndroidViewModelFactory constructed " +
"with empty constructor works only with " +
"create(modelClass: Class<T>, extras: CreationExtras)."
)
} else {
create(modelClass, application)
}
}
@Suppress("DocumentExceptions")
private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
try {
modelClass.getConstructor(Application::class.java).newInstance(app)
} catch (e: NoSuchMethodException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InvocationTargetException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
} else super.create(modelClass)
}
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return try {
modelClass.getDeclaredConstructor().newInstance()
} catch (e: NoSuchMethodException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: InstantiationException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("Cannot create an instance of $modelClass", e)
}
}
创建 ViewModel
都是通过反射的方式创建,支持无参数和只有一个 Application
的构造函数的 ViewModel
。
我们再来看看 ComponentActivity
(它实现了 ViewModelStoreOwner
接口) 中是如何来创建 ViewModelStore
的:
Java
@NonNull
@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();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
OK 我们的主角出现了,通过 getLastNonConfigurationInstance()
方法来获取 ViewModelStore
的,如果没有获取到就创建一个。他也是 Activity
中的标准方法哦,他就相当于 onRestoreInstanceState()
(也就是用来获取上次保存的数据),而对应的 onSaveInstanceState()
(保存数据) 方法就是 onRetainNonConfigurationInstance()
,他们在某些方面和 onSaveInstanceState()
/ onRestoreInstanceState()
确实很像(可以参考我的另外一篇文章: 关于 Activity#onSaveInstanceState() 的笔记)。
我们来看看 ComponentActivity
对应的 onRetainNonConfigurationInstance()
和 getLastNonConfigurationInstance()
的实现:
onRetainNonConfigurationInstance()
:
Java
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
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 nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
他这里还添加了一个 onRetainCustomNonConfigurationInstance()
方法来保存自定义的数据,因为 onRetainNonConfigurationInstance()
已经被 final
修饰了,不让重写,自定义的数据和 ViewModelStore
都会被存放在 NonConfigurationInstances()
中。
getLastNonConfigurationInstance()
直接是 Activity
的默认实现:
Java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
官方文档
onRetainNonConfigurationInstance()
的官方文档:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance. If you are targeting Build.VERSION_CODES.HONEYCOMB or later, consider instead using a Fragment with Fragment.setRetainInstance(boolean.
This function is called purely as an optimization, and you must not rely on it being called. When it is called, a number of guarantees will be made to help optimize configuration switching:
The function will be called between onStop and onDestroy.
A new instance of the activity will always be immediately created after this one's onDestroy() is called. In particular, no messages will be dispatched during this time (when the returned object does not have an activity to be associated with).
The object you return here will always be available from the getLastNonConfigurationInstance() method of the following activity instance as described there.
These guarantees are designed so that an activity can use this API to propagate extensive state from the old to new activity instance, from loaded bitmaps, to network connections, to evenly actively running threads. Note that you should not propagate any data that may change based on the configuration, including any data loaded from resources such as strings, layouts, or drawables.The guarantee of no message handling during the switch to the next activity simplifies use with active objects. For example if your retained state is an android.os.AsyncTask you are guaranteed that its call back functions (like android.os.AsyncTask.onPostExecute) will not be called from the call here until you execute the next instance's onCreate(Bundle). (Note however that there is of course no such guarantee for android.os.AsyncTask.doInBackground since that is running in a separate thread.)
Note: For most cases you should use the Fragment API Fragment.setRetainInstance(boolean) instead; this is also available on older platforms through the Android support libraries.
Returns: any Object holding the desired state to propagate to the next activity instance
我来说说我对文档的理解:
该方法被系统调用,由于配置改变需要销毁当前 Activity
,然后立马会创建一个新配置的 Activity
,你就可以通过这个方法来保存销毁的 Activity
的数据,你可以返回任何对象来保存数据,在新的 Activity
中可以通过 getLastNonConfigurationInstance()
方法来获取旧 Activity
中保存的数据 (注意对比和 onSaveInstanceState()
方法的描述)。
该方法只是纯粹的优化,你不能依赖它的调用,当它被调用时会有以下几点保证帮助你优化配置切换:
-
该方法会在
onStop
和onDestroy
之间调用。 -
旧的
Activity
onDestroy()
方法被调用后,新的Activity
会被立即创建。 -
旧的
Activity
返回的数据,在新的Activity
可以通过getLastNonConfigurationInstance()
获取。
该 API 是设计用来从旧的 Activity
向新的 Activity
来传递状态(针对配置改变这种情况),从 bitmap
到网络连接再到运行的线程,都是可以的。但是你不能传递和配置改变相关的数据,其中包括从 resources
中获取的数据,比如 string
, layout
和 drawable
等。(这也很好理解本来 string
这些都依赖配置,比如你切换语言了,你还用之前语言对应的 string
这肯定不对,这也解决了我的一个疑问为什么叫 onRetainNonConfigurationInstance
,哈哈哈哈😂)
最后还说到 Framget
可以通过 setRetainInstance()
方法来保存对应的实例。
然后再看看 getLastNonConfigurationInstance
的文档描述:
Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate and onStart calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.
Note that the data you retrieve here should only be used as an optimization for handling configuration changes. You should always be able to handle getting a null pointer back, and an activity must still be able to restore itself to its previous state (through the normal onSaveInstanceState(Bundle) mechanism) even if this function returns null.
Note: For most cases you should use the Fragment API Fragment.setRetainInstance(boolean) instead; this is also available on older platforms through the Android support libraries.
Returns:
the object previously returned by onRetainNonConfigurationInstance()
都没什么好看的了,onRetainNonConfigurationInstatce()
方法中已经讲得很清楚了,我不想再翻译了😂 (只是因为强迫症贴出来)
从源码来看看 Activity 重启过程
源码阅读基于 Android 9
在 关于 Activity#onSaveInstanceState() 的笔记)这篇文章中我有讲到 AMS
控制 Activity
的 onStop
生命流程,AMS
会通过 binder
向应用进程发送一个 Transaction
,这个 Transaction
中会包含一个和对应生命周期的对象叫 StopActivityItem
,应用进程收到这个任务后就会在主线程执行相关的生命周期,具体的执行流程如果不清楚的话,可以看看前面提到的文章。
重启过程也是和 onStop
生命周期类似的,只是对应的执行生命周期的对象是 ActivityRelaunchItem
,我们也是从 ActivityRelaunchItem#execute()
方法当入口函数来分析:
Java
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
if (mActivityClientRecord == null) {
if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
return;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
client
就是对应的 ActivityThread
对象,这里直接调用了它的 handleRelauchActivity()
方法。
Java
@Override
public void handleRelaunchActivity(ActivityClientRecord tmp,
PendingTransactionActions pendingActions) {
// ...
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
// ...
}
在 handleRelaunchActivity()
中又调用了一个关键方法 handleRelaunchActivityInner()
方法。
Java
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
PendingTransactionActions pendingActions, boolean startsNotResumed,
Configuration overrideConfig, String reason) {
// ...
handleDestroyActivity(r.token, false, configChanges, true, reason);
// ...
handleLaunchActivity(r, pendingActions, customIntent);
}
这个地方就很关键了,首先会调用 handleDestroyActivity()
方法来销毁当前的 Activity
,注意它这里传递的 getNonConfigInstance
参数为 true
,对,这就是控制 onRetainNonConfigurationInstance()
方法的回调,如果是正常销毁这个参数是 false
;然后接着就是通过 handleLaunchActivity()
方法立马启动新的 Activity
。
重启 Activity
和其他正常情况创建 Activity
和销毁 Activity
还是有一些区别。正常的销毁 Activity
,onPause
与 onStop()
都需要单独等待 AMS
发送过来的 Transaction
,新的 Activity
创建也是要单独等待 AMS
发送过来的 Transaction
;而在重启流程中 handleDestroyActivity
会直接调用销毁的 Activity
的 onPause()
,onStop()
,onDestroy()
生命周期,然后再创建新的 Activity
然后调用 onCreate()
,onStart()
,onResume()
生命周期。
我在 Activity#onDestroy()
延迟回调的文章中有提到 Activity
的销毁过程,感兴趣可以看看:Activity onDestroy 生命周期延迟回调原理
我们先看旧 Acitivity
销毁方法 handleDestroyActivity()
的源码:
Java
@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance, reason);
// ...
}
接着看 performDestroyActivity()
方法:
Java
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");
}
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
// ...
}
}
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity);
// ...
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
// ...
}
r.setState(ON_DESTROY);
}
// ...
return r;
}
如果没有执行 onPause()
就先执行,如果没有执行 onStop()
就先执行,前面说到我们的这种情况下 getNonConfigInstance
为 true
,然后会调用 Activity#retainNonConfigurationInstances()
方法,最终返回的对象就包含我们在 onRetainNonConfigurationInstances()
返回的对象,然后存放在 ActivityClientRecord
中的 lastNonConfigurationinstances
变量中,最后执行 onDestroy()
流程。
我们继续看看创建新的 Activity
的 handleLaunchActivity()
方法:
Java
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
// ...
final Activity a = performLaunchActivity(r, customIntent);
// ...
return a;
}
然后我们继续看 performLaunchActivity()
方法:
Java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
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) {
// ...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// ...
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.configCallback);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
// ...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
// ...
}
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
// ...
}
return activity;
}
首先通过反射创建一个 Activity
对象,然后调用 attach()
方法,在这个方法中就会传递前面在 ActivityClientRecord.lastNonConfigurationInstances
赋值的对象,然后执行 onCreate()
生命周期。
我们看看 Activity
的 attach()
方法:
Java
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) {
// ...
mLastNonConfigurationInstances = lastNonConfigurationInstances;
// ...
}
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
上面的代码朴实无华,不想多解释。
总结
ViewModel
能够保存重建 Activity
的数据是因为使用了 onRetainNonConfigurationInstances()
这个接口来保存数据,主要的工作逻辑就是 AMS
通知应用进程重建后,应用进程会销毁当前的 Activity
同时通过 onRetainNonConfigurationInstances()
接口来获取需要保存的对象,然后把他赋值到 ActivityClientRecord
中,销毁旧的 Activity
后会立即重建新的 Activity
,在新的 Activity
的 attach()
把之前保存的数据传递过去,这样新的 Activity
就拿到了上次的数据。
它和 onSaveInstanceState()
方法也有很多相似与不同,我们再来对比一下他们。
触发时机不同
onRetainNonConfigurationInstantces()
明确了是由于配置改变 导致的 Activity
会被立即销毁 ,然后立即重建的时候才会触发而且是一定会触发 ,比如旋转屏幕、屏幕大小改变还有设置中语言等等改变时。
onSaveInstantceState()
当 Activity
有可能被回收但后续需要恢复的情况下会触发 ,官方有一个很好例子来说明 A
启动 B
后,由于系统资源不足就会回收 A
,但是有可能后续还要使用 A
,然后就会触发 A
的 onSaveInstantceState()
方法来保存数据,当 B
退出后,就需要重建 A
,然后把上次的数据传递给新的 A
。
onSaveInstantceState()
的触发范围比 onRetainNonConfigurationInstantces()
要广,配置改变引起的重建 onSaveInstantceState()
与 onRetainNonConfigurationInstantces()
都会触发(这个我有测试); 当系统资源不足引起回收只有 onSaveInstantceState()
会触发(这点我没有验证,有可能是错的,你可以自己去试试看)。
数据保存的方式不同
onRetainNonConfigurationInstantces()
直接把对象保存在当前进程的 ActivityClientRecord
中,回收时和恢复时都是同一个对象。
onSaveInstanceState()
需要通过 binder
跨进程通信保存在系统进程 AMS
的 ActivityRecorder
中,回收前和恢复时不是同一个对象,但是数据是一样的。
这也解释了为什么 onSaveInstanceState()
中存储的对象是需要支持序列化的,而 onRetainNoConfigurationInstantces()
是可以保存任意对象的。