ViewModel的创建、销毁和恢复

ViewModel 概览

Google官方将ViewModel定义为一种业务逻辑或屏幕级状态容器------用于封装业务逻辑,以及向UI界面提供和公开状态。它具有三个特点或者优势:

  • ViewModel支持持久的保留状态:它可以缓存状态,并可在配置更改后持久保留相应状态。例如,再进行配置更改后(例如旋转屏幕时),无需重新获取数据即可直接,Activity即可从中拿出旋转之前的数据;
  • ViewModel具备生命周期感知能力:它可以感知到Activity/Fragment的生命周期,通过执行操作来响应它们的生命周期状态变化,这样有助于精简代码提高程序的可维护性;
  • 分离业务的UI,控制对业务的访问:ViewModel可将界面相关的业务逻辑分离出来,更好的管理和处理业务逻辑。

ViewModel的基本使用

可以直接通过继承的方式定义一个你所需要的ViewModel类:

kotlin 复制代码
class MainViewModel : ViewModel() {
    val name = "MainViewModel"
}

然后,您可以从 activity 访问 ViewModel,如下所示:

kotlin 复制代码
class MainActivity : ComponentActivity() {
    private val mainViewModel : MainViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
    }
}

拿到ViewModel的实例之后,就可以通过对ViewModel的成员变量或者函数的调用进行数据的访问和控制。

ViewModel的创建

上文中是通过Kotlin的委托来创建ViewModel的,它本质是借助ComponentActivity扩展函数来实现的。其中细节和原理这里不再展开,其最终也是借助ViewModelProvider 实现创建的,对用Java下的实现如下:

scala 复制代码
public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MainViewModel model = new ViewModelProvider(this).get(MainViewModel.class);
    }
}

ViewModelProvider的创建流程核心代码如下:

kotlin 复制代码
public open class ViewModelProvider

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,
) { 
 // ...
}    

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)
}

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
    } 
    val extras = MutableCreationExtras(defaultCreationExtras)
    extras[VIEW_MODEL_KEY] = key
    return try {
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        factory.create(modelClass)
    }.also { store.put(key, it) }
}

ViewModel的整体创建流程是比较简洁和清晰的。 ViewModelProvider 需要三个参数:

  • ViewModelStore 用于存储和管理ViewModel实例
  • Factory 创建ViewModel的工厂类
  • CreationExtras 用于为构造函数有依赖项的ViewModel提供依赖项

可以预知到整体的创建流程:

Activity借住ViewModelStore 管理所有ViewModel,它是一个Map结构的数据,使用类名canonicalName 作为key值。创建之前首先判断ViewModelStore是否已经包含了当前类的实例,如果有则直接返回。否则的话通过Factory创建一个新的实例,添加到ViewModelStore 并返回给调用者。以此实现了单例模式,确保了反复调用永远之返回同一个实例。

其中,FactoryViewModelProvider 内置实现为例:

kotlin 复制代码
public open class NewInstanceFactory : Factory {
    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)
        }
    }
}

其核心是通过反射创建对应类的实例。而CreationExtras 并不是本文的重点。这里只需要明白其核心是通过工厂类,借助反射创建实例即可。

重点关注ViewModelStore 的来源和其管理ViewModel的方式。

ViewModelStore

ViewModelStore的代码非常简单:

kotlin 复制代码
open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }
    
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

短短几十行代码,其核心也很明确清晰:

  • 通过一个Map作为容器存储ViewModel
  • 提供插入获取和清除操作

那么问题来了,ViewModelStore 是谁在持有,又是在哪里创建的呢?

回到上文的代码:

kotlin 复制代码
MainViewModel model = new ViewModelProvider(this).get(MainViewModel.class);

public open class ViewModelProvider

public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

ViewModelProvider 接受一个ViewModelStoreOwner ,而ViewModelStoreOwner 则是ViewModelStore 的持有者。ViewModelStoreOwner 是一个接口,其源码实现如下:

kotlin 复制代码
interface ViewModelStoreOwner {
    val viewModelStore: ViewModelStore
}

而它最终是在我们的Activity的父类ComponentActivity里被实现了:

kotlin 复制代码
open class ComponentActivity() : androidx.core.app.ComponentActivity(),ViewModelStoreOwner {
    private var _viewModelStore: ViewModelStore? = null

    override val viewModelStore: ViewModelStore
        get() {
            ensureViewModelStore()
            return _viewModelStore!!
        }

    
private fun ensureViewModelStore() {
    if (_viewModelStore == null) {
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            _viewModelStore = nc.viewModelStore
        }
        if (_viewModelStore == null) {
            _viewModelStore = ViewModelStore()
        }
    }
}

上述代码是精简之后的代码。正常流程下会走nc 为null的流程,lastNonConfigurationInstance 会在屏幕配置发生变化(如屏幕旋转)后起到数据恢复的作用。这里先关注主流程ViewModelStore 的创建。核心有两点:

  • Activity是ViewModelStore 的持有者:通过实现ViewModelStoreOwner 获得向外部提供ViewModelStore的能力
  • Activity采用了饿汉式单例创建和管理ViewModelStore ,每当有调用者获取ViewModelStore 时,首先判断是否已经创建过。确保当前Activity内有且只有一个ViewModelStore

至此我们大致已经梳理清ViewModel的创建流程了,它们整体的对应关系如下:

Activity通过ViewModelStore 持有并管理ViewModel ,Activity和ViewModelStore 之间是一对一的关系。且使用了单例模式确保ViewModelStore 的唯一性。而ViewModelStore 是一个通过一个Map存储和管理所有ViewModel ,并通过ViewModelProvider 来实现单例,保证同一ViewModel 在同一Activity里只有一个。

接下来就是ViewModel的销毁流程了。

ViewModel的销毁

先贴出官方对其生命周期的结束图:

ViewModel 的生命周期与其作用域直接关联。ViewModel 会一直保留在内存中,直到其作用域 ViewModelStoreOwner 消失。它是贯穿整个Activity的生命周期的。只在Activity销毁时才会被销毁。

ViewModel 提供了onCleared 回调函数,供使用者做一些销毁工作。参考上文中ViewModelStore的代码,销毁是在ViewModelStore 中被统一调用的:

kotlin 复制代码
fun clear() {
    for (vm in map.values) {
        vm.clear()
    }
    map.clear()
}

ViewModelStore 的clear会遍历所有已经存在的ViewModel 实例,并逐个销毁。追根溯源,clear是在ComponentActivity 中被调用的:

scss 复制代码
init {
    lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            // Clear out the available context
            contextAwareHelper.clearAvailableContext()
            // And clear the ViewModelStore
            if (!isChangingConfigurations) {
                viewModelStore.clear()
            }
            reportFullyDrawnExecutor.activityDestroyed()
        }
    })
}

Activity在初始化时,通过init函数添加Lifecycle监听,通过Lifecycle 监听到Activity的销毁,然后调用ViewModelStore 的clear函数。Lifecycle是通过为每个Activity添加ReportFragment 实现对Activity的生命周期监听的,这里暂不对其绑定逻辑进行分析,只展示相关声明周期回调位置:

kotlin 复制代码
open class ReportFragment() : android.app.Fragment() {

    override fun onStart() {
        super.onStart()
        dispatchStart(processListener)
        dispatch(Lifecycle.Event.ON_START)
    }

    override fun onResume() {
        super.onResume()
        dispatchResume(processListener)
        dispatch(Lifecycle.Event.ON_RESUME)
    }

    override fun onPause() {
        super.onPause()
        dispatch(Lifecycle.Event.ON_PAUSE)
    }

    override fun onStop() {
        super.onStop()
        dispatch(Lifecycle.Event.ON_STOP)
    }

    override fun onDestroy() {
        super.onDestroy()
        dispatch(Lifecycle.Event.ON_DESTROY)
        processListener = null
    }
}

Lifecycle.Event.ON_DESTROY 是在Fragment的onDestroy中触发的,它和Activity的 onDestroy 一样最终由Activity中的performDestroy 触发。对应的可以理解为是ViewModel在Activity的onDestroy 时被销毁。

ViewModel的恢复

到此时,还有两个疑惑点没有解释清楚:

  • 上文中viewModelStore.clear() 调用前为什么要先判断!isChangingConfigurations
  • 既然ViewModel是在performDestroy 中被销毁的,屏幕发声旋转时也会触发该函数,那ViewModel是怎么做到在屏幕旋转时不会丢失的呢?

答案就蕴藏在问题问题中,Activity正是通过isChangingConfigurations 来判断ViewModel是否需要销毁的,在我们最终所继承的Activity中有如下代码:

typescript 复制代码
public class Activity extends ContextThemeWrapper {

	  boolean mChangingConfigurations = false;

		NonConfigurationInstances mLastNonConfigurationInstances;

    public boolean isChangingConfigurations() {
        return mChangingConfigurations;
    }
    
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

    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,
        IBinder shareableActivityToken) {
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
    }
}

这里里面就涉及到了一部分Activity的生命周期流转,相关知识可以查看:Activity的生命周期流转过程。我们需要知道在屏幕发生旋转时Activity的生命周期流转都是通过handleRelaunchActivity 触发的。大致执行流程如下:

其中:mChangingConfigurations 负责标记Activity在销毁时是否调用viewModelStore.clear() ,而mLastNonConfigurationInstances 则在Activity attach时接受来着ActivityThread 传递过来的NonConfigurationInstances 实例。

首先看mChangingConfigurations状态标记,当屏幕旋转时,在Activity销毁之前。ActivityThreadhandleRelaunchActivity 会被触发。此时,mChangingConfigurations 会被修改为true。

ini 复制代码
//ActivityThread.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
public void handleRelaunchActivity(ActivityClientRecord tmp,
    PendingTransactionActions pendingActions) {
        ActivityClientRecord r = mActivities.get(tmp.token);
        r.activity.mChangingConfigurations = true;
}

这样在Lifecycle.Event.ON_DESTROY 被触发时,由于mChangingConfigurations 为true,viewModelStore.clear() 函数就不会执行。因此,Activity的所有ViewModel的clear 函数也不会执行。

然而,这只是ViewModelclear 函数不会执行的原因。ViewModel是通过ViewModelStore 被Activity所持有的,整个Activity都被销毁了,也意味着它所包含的所有子类都会被销毁,那么ViewModel又是怎么恢复的呢?

关键就在mLastNonConfigurationInstances 。在Activity的attach 函数被执行时,Activity都会接受一个lastNonConfigurationInstances 参数 。而这个参数有什么用呢,回顾上文中的ViewModelStore 创建流程代码:

kotlin 复制代码
    
private fun ensureViewModelStore() {
    if (_viewModelStore == null) {
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            _viewModelStore = nc.viewModelStore
        }
        if (_viewModelStore == null) {
            _viewModelStore = ViewModelStore()
        }
    }
}

这段代码只有在_viewModelStorenc.viewModelStore 都为空时才会创建一个新的ViewModelStore 。很明显,Activity是通过借助mLastNonConfigurationInstances 保存ViewModelStore 进而实现当前Activity全部ViewModel的恢复的。接下来就是只需找到Activity是何时将ViewModelStore 存储起来的了。

它是在ComponentActivityonRetainNonConfigurationInstance 函数中被存储的,代码如下:

kotlin 复制代码
final override fun onRetainNonConfigurationInstance(): Any? {
    var viewModelStore = _viewModelStore
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        val nc = lastNonConfigurationInstance as NonConfigurationInstances?
        if (nc != null) {
            viewModelStore = nc.viewModelStore
        }
    }
    if (viewModelStore == null && custom == null) {
        return null
    }
    val nci = NonConfigurationInstances()
    nci.viewModelStore = viewModelStore
    return nci
}

该函数返回一个NonConfigurationInstances 实例,其持有当前Activity的ViewModelStore 实例。而onRetainNonConfigurationInstance 调用流程如下:

ini 复制代码
//Activity.java
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    return nci;
}
arduino 复制代码
//ActivityThread.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
		ActivityClientRecord r = mActivities.get(tmp.token);
		handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}

 private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
		handleDestroyActivity(r, false, configChanges, true, reason);
}

public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
		performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);          
}            
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
    int configChanges, boolean getNonConfigInstance, String reason) {
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        mInstrumentation.callActivityOnDestroy(r.activity);
}

ViewModelStore 在Activity销毁之前,会作为ActivityClientRecord 的实例存储到存储到 ActivityThreadmActivities中。

至此,ViewModelStore 的保存和恢复流程已结束,整体流程如下:

  • 在屏幕旋转时,ActivityThread在销毁Activity之前通过onRetainNonConfigurationInstance获取当前Activity的ViewModelStore
  • ActivityThread借助ActivityClientRecordViewModelStore 存储在变量mActivities
  • 每当Activity执行attach 函数时,ActivityThread都会从mActivities 中获取已保存的lastNonConfigurationInstances 并赋值给Activity的mLastNonConfigurationInstances 成员变量
  • 最后,当需要访问ViewModel时,直接从mLastNonConfigurationInstances 获取屏幕旋转前已经创建好的ViewModel。

总结

  • Activity通过一个Map结构的ViewModelStore 管理ViewModel,并借助ViewModelProvider 穿件ViewModel并实现单例
  • Activity借助ActivityThread,通过将ViewModelStore 实例存储在ActivityThread的成员变量mActivities 中实现ViewModelStore 的保存
  • ActivityThread在调用Activity的attach 之前,首先通过mActivities获取到目标Activity的ActivityClientRecord,并将其中的lastNonConfigurationInstance 传递给目标Activity。而Activity则借助其判断是否需要重新创建ViewModelStore 。并间接的实现ViewModel的恢复功能。
相关推荐
x02414 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton17 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss17 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳20 天前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生24 天前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm
alexhilton1 个月前
让Activity更加优雅地跳转
android·kotlin·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-11.客户端操作属性
android·android studio·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack