Jetpack之Lifecycle原理剖析:解锁高效组件生命周期管理
深入剖析Jetpack之LiveData原理:构建稳健高效的Android数据架构
Jetpack Compose 巅峰架构:ViewMde+Flow+Retrofit,重塑 Android 开发新格局
深入剖析ViewModel:应对屏幕旋转、系统配置变动与进程回收的数据管理之道
概述
在Android开发的广袤天地里,屏幕旋转、系统配置变动以及进程意外回收等情况,犹如隐藏在暗处的“小怪兽”,时不时给开发者们带来数据管理方面的挑战。尤其是Activity因这些情况而销毁重建时,如何确保数据不丢失,成为了开发者们必须攻克的难题。而ViewModel的出现,就像是手持魔法宝剑的勇士,巧妙地化解了这一困境。今天,就让我们一同揭开ViewModel在这些复杂场景下数据管理的神秘面纱。
一、问题的引出:那些令人头疼的场景
在日常使用手机应用的过程中,屏幕旋转是再常见不过的操作了。可别小瞧这个简单的动作,它却能在Android开发中掀起“波澜”。当进行屏幕旋转,或者切换系统语言、更改字体大小等系统配置变动时,Activity的生命周期会经历一次完整的从销毁到重建的过程。
想象一下,用户在一款笔记应用中精心编辑了一段文字,正准备点击保存,这时不小心旋转了屏幕,结果编辑的内容全部丢失,这体验得多糟糕!不仅如此,在某些极端情况下,系统可能会因为内存不足等原因回收应用进程,这同样会导致Activity销毁重建,数据的完整性面临巨大威胁。
但神奇的是,我们会发现ViewModel中的变量值在这些场景下却能“稳如泰山”,不受影响。这背后究竟隐藏着怎样的奥秘呢?接下来,让我们深入代码的世界一探究竟。
二、获取ViewModel实例:关键步骤解析
在代码里,获取ViewModel实例的常见代码如下:
// MainActivity.kt
val viewModel = ViewModelProvider.create(viewModelStore).get(MainViewModel::class)
为了更透彻地理解这行代码,我们把它拆分成两部分来分析:ViewModelProvider.create(viewModelStore).
和 get(MainViewModel::class)
。
(一)ViewModelProvider.create(viewModelStore):实例获取的起点
ViewModelProvider.create(viewModelStore).
的主要职责是获取 ViewModelProvider
实例。我们来看看相关的源码:
// ViewModelProvider.kt
public constructor(
store: ViewModelStore,
factory: Factory,
defaultCreationExtras: CreationExtras = CreationExtras.Empty
) : this(ViewModelProviderImpl(store, factory, defaultCreationExtras))
// ComponentActivity.kt
override val viewModelStore: ViewModelStore
get() {
checkNotNull(application) {
("Your activity is not yet attached to the " +
"Application instance. You can't request ViewModel before onCreate call.")
}
ensureViewModelStore()
return _viewModelStore!!
}
private fun ensureViewModelStore() {
if (_viewModelStore == null) {
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
// 从NonConfigurationInstances恢复ViewModelStore
_viewModelStore = nc.viewModelStore
}
if (_viewModelStore == null) {
_viewModelStore = ViewModelStore()
}
}
}
从这些代码可以看出,最终调用的是 ComponentActivity
中的 ensureViewModelStore()
方法。这里面涉及到两个极为重要的类:ViewModelStore
和 NonConfigurationInstances
。
ViewModelStore
,从名字就能猜到,它就像是一个“数据仓库”,专门用来存储ViewModel对象,起到缓存的作用,其底层是通过 HashMap
实现的。来看它的源码:
// ViewModelStore.java
public open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
public fun put(key: String, viewModel: ViewModel) {
val oldViewModel = map.put(key, viewModel)
oldViewModel?.clear()
}
/**
* 返回与给定`key`映射的`ViewModel`,如果不存在则返回null。
*/
public operator fun get(key: String): ViewModel? {
return map[key]
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun keys(): Set<String> {
return HashSet(map.keys)
}
/**
* 清除内部存储,并通知`ViewModel`它们不再被使用。
*/
public fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}
NonConfigurationInstances
是 ComponentActivity
的一个内部类,虽然结构简单,但作用重大。它里面包含 viewModelStore
和 custom
等字段,代码如下:
// ComponentActivity$NonConfigurationInstances.kt
internal class NonConfigurationInstances {
var custom: Any? = null
var viewModelStore: ViewModelStore? = null
}
(二)get(MainViewModel::class):实例的查找与创建
接着看 get(MainViewModel::class)
这部分代码:
// ViewModelProvider.kt
public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
impl.getViewModel(modelClass)
// ViewModelProviderImpl.kt
internal class ViewModelProviderImpl(
private val store: ViewModelStore,
private val factory: ViewModelProvider.Factory,
private val extras: CreationExtras
) {
constructor(
owner: ViewModelStoreOwner,
factory: ViewModelProvider.Factory,
extras: CreationExtras
) : this(owner.viewModelStore, factory, extras)
@Suppress("UNCHECKED_CAST")
internal fun <T : ViewModel> getViewModel(
modelClass: KClass<T>,
key: String = ViewModelProviders.getDefaultKey(modelClass)
): T {
val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
if (factory is ViewModelProvider.OnRequeryFactory) {
factory.onRequery(viewModel!!)
}
return viewModel as T
} else {
if (viewModel != null) {
}
}
val extras = MutableCreationExtras(extras)
extras[ViewModelProviders.ViewModelKey] = key
return createViewModel(factory, modelClass, extras).also { vm -> store.put(key, vm) }
}
}
这部分代码的逻辑是,先根据 key
从 ViewModelStore
中查找缓存的ViewModel。如果找到了,并且该ViewModel的类型符合要求,就直接返回这个实例;要是没找到,就会通过工厂方法创建一个新的ViewModel,然后把它存入 ViewModelStore
缓存起来。
在Activity中,我们一般没有设置自定义的 key
,默认的 key
是一个常量。基于此,我们推测:在屏幕旋转前后,ViewModelStore
是同一个对象,获取到的 viewModel
也应该是同一份实例对象。为了验证这个推测,我们可以通过日志打印来查看:
val viewModel = ViewModelProvider.create(viewModelStore).get(ComposeViewModel::class)
Log.i("TAG--> ", "viewModel--> $viewModel" + " viewModelStore--> $viewModelStore")
实际运行时,打印结果显示,屏幕旋转后,ViewModelStore
和 ViewModel
的对象地址确实没有变化,这就证实了我们的猜测。
三、ViewModelStore的恢复与存储机制
(一)viewModelStore的恢复
mViewModelStore
的恢复过程在 ComponentActivity
的 ensureViewModelStore()
方法中:
// ComponentActivity.kt
override val viewModelStore: ViewModelStore
get() {
ensureViewModelStore()
return _viewModelStore!!
}
private fun ensureViewModelStore() {
if (_viewModelStore == null) {
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
// 从NonConfigurationInstances恢复ViewModelStore
_viewModelStore = nc.viewModelStore
}
if (_viewModelStore == null) {
_viewModelStore = ViewModelStore()
}
}
}
可以看到,屏幕旋转后,Activity重建时会从 getLastNonConfigurationInstance()
方法中获取之前保存的 NonConfigurationInstances
实例对象,然后从中取出存储的 viewModelStore
对象。这也就意味着,getLastNonConfigurationInstance()
方法会在 onCreate()
方法之前调用。
(二)ViewModelStore的存储
既然屏幕旋转后 ViewModelStore
是从 NonConfigurationInstances
获取的,那么在屏幕旋转前,它肯定也是存放在这里的。查看相关代码可以发现:
// ComponentActivity.kt
final override fun onRetainNonConfigurationInstance(): Any? {
val custom = onRetainCustomNonConfigurationInstance()
var viewModelStore = _viewModelStore
if (viewModelStore == null) {
// 调用getViewModelStore(),检查上一个NonConfigurationInstance中是否有已存在的ViewModelStore
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
viewModelStore = nc.viewModelStore
}
}
if (viewModelStore == null && custom == null) {
return null
}
// 这里进行存储
val nci = NonConfigurationInstances()
nci.custom = custom
nci.viewModelStore = viewModelStore
return nci
}
由此可知,屏幕旋转前,数据是在 onRetainNonConfigurationInstance()
方法中保存的。
四、数据存储与恢复的深度探究
(一)Activity重启后数据的恢复
Activity重建后,数据是在 getLastNonConfigurationInstance()
中恢复的。相关源码如下:
// Activity.java
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
mLastNonConfigurationInstances
的赋值是在 Activity
的 attach()
方法中:
// Activity.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, IBinder assistToken) {
attachBaseContext(context);
···
mLastNonConfigurationInstances = lastNonConfigurationInstances;
···
}
从Activity的启动流程可知,Activity.attach()
方法是由 ActivityThread
调用的:
// ActivityThread.java
Activity.NonConfigurationInstances lastNonConfigurationInstances;
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
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);
}
这表明,数据实际上存储在 ActivityClientRecord
中。在Activity启动时,ActivityThread
会将 ActivityClientRecord
中的 lastNonConfigurationInstances
通过 attach()
方法传递给对应的Activity,然后Activity再通过 getLastNonConfigurationInstance()
方法恢复数据。
(二)屏幕旋转前数据的存储
屏幕旋转前,数据在 onRetainNonConfigurationInstance()
方法中保存,而这个方法是在 Activity
的 retainNonConfigurationInstances()
方法中被调用的。进一步查看发现,retainNonConfigurationInstances()
方法是在 ActivityThread
的 performDestroyActivity()
方法中被调用的:
// ActivityThread.java
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
···
if (getNonConfigInstance) {
try {
r.lastNonConfigurationInstances
= r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
···
}
}
···
return r;
}
可以看出,performDestroyActivity()
方法调用了 retainNonConfigurationInstances()
方法,并把数据保存到了 ActivityClientRecord
的 lastNonConfigurationInstances
中。
五、特例:系统杀后台的情况
在实际开发过程中,我们通过实验发现了一个特殊情况:当系统杀后台时,Activity不会执行 onDestory()
和 onRetainCustomNonConfigurationInstance()
方法。这就意味着Activity销毁的生命周期不会正常执行,只有当App再次回到前台时,才会执行Activity重建的生命周期。
由于没有执行 onRetainCustomNonConfigurationInstance()
方法,Activity的数据没有被缓存下来,所以Activity重建时也就没有数据可以恢复。从Activity中打印ViewModel实例对象的地址就能发现,使用 adb
模拟杀掉后台后,ViewModel的地址值发生了变化,变成了一个全新的地址,这说明原来的ViewModel实例已经不存在了。
六、总结
(一)屏幕旋转前,Activity销毁时
ComponentActivity
会调用 onRetainNonConfigurationInstance()
方法,将即将销毁的Activity的 ViewModelStore
转化为 NonConfigurationInstances
对象。接着,会继续调用 Activity
的 retainNonConfigurationInstances()
方法,最终在 ActivityThread
的 performDestroyActivity()
方法中将数据保存在 ActivityClientRecord
中。
(二)Activity重建后
在Activity启动时,ActivityThread
会调用 performLaunchActivity()
方法,将存储在 ActivityClientRecord
中的 lastNonConfigurationInstances
通过 Activity
的 attach()
方法传递到对应的Activity中。然后,Activity通过 getLastNonConfigurationInstance()
恢复 ViewModelStore
实例对象,最后根据对应的 key
拿到销毁前对应的 ViewModel
实例。
需要特别注意的是,当系统内存不足回收后台应用时,ViewModel中的数据默认是不会恢复的。在开发过程中,我们要根据实际业务需求,考虑是否需要额外的机制来处理这种情况,比如将关键数据持久化存储到本地文件或者数据库中。
通过以上对ViewModel在屏幕旋转、系统配置变动以及进程意外回收场景下数据管理原理的深入探究,相信大家对它的工作机制有了更清晰的认识。这不仅有助于我们在开发中更高效地利用ViewModel来管理数据,还能让我们在遇到相关问题时,能够迅速定位问题根源并找到解决方案。希望这篇文章能为大家的Android开发之路提供一些帮助!
网友评论