Android 之 ViewModel 相关知识总结

作用

  • 用来联系 View 和 model 之间的关系,存储和管理界面相关的数据
  • ViewModel的生命周期跟随页面生命周期,页面销毁,自动清除ViewModel,防止内存泄漏
  • 不会因为屏幕旋转而销毁,减少了维护状态的工作

主要的类

分析版本:2.3.1。

  • ViewModelProvider:获取和管理 ViewModel 实例的类。
  • ViewModelFactory:自定义创建 ViewModel 实例的工厂类。
  • ViewModelStore:持有 ViewModel 实例的容器类。它在配置更改时(如屏幕旋转)存储和管理 ViewModel 实例,以便在恢复后重新获取相同的 ViewModel 实例。
  • ViewModelStoreOwner:通常,Activity 和 Fragment 实现了 ViewModelStoreOwner 接口,因此它们可以存储 ViewModel 实例并与生命周期相关联。

创建过程

kotlin 复制代码
class TestViewModel : ViewModel() {

    val data = MutableLiveData<Int>(0)

    override fun onCleared() {
        super.onCleared()
    }
}

//在Activity中使用,通过 by viewModels() 进行获取
val model: TestViewModel by viewModels()
或者
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)

by viewModels()

  • factoryProducer:用于实现自定义 ViewModelFactory,不传的话,默认为ComponentActivity#getDefaultViewModelProviderFactory。
  • storeProducer:一般指的就是 Activity 或者 Fragment。
  • 最后通过 ViewModelProvider#get 方法,获取到指定 Class 的 ViewModel 对象。
kotlin 复制代码
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }

    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}


public class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}

ViewModelStoreOwner

  • ViewModelStoreOwner,一般指的就是 Activity 和 Fragment 了。
  • 可以关注一下 ComponentActivity 里面的 getViewModelStore() 实现。
  • 一般情况下,启动 Activity 之后,是通过 new 创建的 ViewModelStore。
  • 比如屏幕旋转,数据恢复,当 getLastNonConfigurationInstance 不为空的时候,可以从 NonConfigurationInstances 中去读取的ViewModelStore 进行赋值。

ViewModelStore

  • 逻辑比较简单,就是使用 Map,存储已创建的 ViewModel。
  • 在 Activity 或者 Fragment 在销毁的时候,调用 clear 方法。

ViewModelProvider

  • 一般情况下,都是通过 ViewModelProvider#get(java.lang.Class) 来获取 ViewModel。
  • 不设置key值的话:key值,默认是 "androidx.lifecycle.ViewModelProvider.DefaultKey"+ ViewModel的类名。
  • 通过 key 去 ViewModelStore 获取 ViewModel,不为空,直接返回。
  • 若为空,通过 ViewModelFactory#create 方法进行创建,然后再放入 ViewModelStore。
  • 另一种获取 ViewModel 的方法,ViewModelProvider#get(key, modelClass),可以配合 KeyedFactory 进行使用。可以在同一个Activity下,根据 key 进行区分,创建多个同种 Class 的 ViewModel。(后面再细讲)

恢复过程

  • 保存 lastNonConfigurationInstance
  • ActivityThread的performDestroyActivity 方法中,在 Activity 销毁的时候,将存有 viewModelStore 的NonConfigurationInstances 保存到了 ActivityClientRecord 中。
  • handleRelaunchActivity 中通过获取刚刚的 ActivityClientRecord,再拿到 NonConfigurationInstances,最后可以拿到之前的 ViewModelStore。
  • ViewModelStore 里面的 ViewModel对象,以及里面的各种数据,比如 LiveData 数据,都是之前存在的。所以直接 observe LiveData,就可以恢复 UI 了。
  • 恢复lastNonConfigurationInstance

ViewModelScope 原理

  • viewModelScope 创建的协程的生命周期和 viewModel 的生命周期是绑定的。当 viewModel 被取消的时候,viewModelScope 创建的协程也会被取消。
  • 而 ViewModel 的生命周期和 Activity 或 fragment 绑定,所以在页面销毁的时候,我们就不需要手动去做一些释放资源的工作了。
  • 创建 CoroutineScope ,并通过 setTagIfAbsent 方法,存到 ViewModel 里面的 mBagOfTags 里面。
  • mBagOfTags 是一个 HashMap,可以用来存放一些数据。
  • 当 ViewModel#clear 方法被执行的时候,如果是一个 Closeable 对象的话,会调用它的close方法。
  • viewModelScope 是一个实现 Closeable 接口的 CoroutineScope,close 方法里面会取消协程。

ViewModel 泄露检测

这里分析一下 LeakCanary 里面的实现 ViewModelClearedWatcher。

  1. ViewModelClearedWatcher,本身是一个 ViewModel,通过反射可以获取到 ViewModelStore 里面的mMap,mMap存储了所有的 ViewModel 对象。
  2. 当 Activity 或者 Fragment onCreate 的时候,ViewModelClearedWatcher#install 方法被执行,创建 ViewModelClearedWatcher。
  3. 当 Activity或者 Fragment 被销毁的时候,ViewModelClearedWatcher#onCleared 方法被执行。
  4. 这个时候,通过 ReachabilityWatcher#expectWeaklyReachable,把 mMap 里面的所有 ViewModel 对象加入到 LeakCanary的检测队列里面。
kotlin 复制代码
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
  // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
  // does not have ViewModelStore#keys. All versions currently have the mMap field.
  private val viewModelMap: Map<String, ViewModel>? = try {
    val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
    mMapField.isAccessible = true
    @Suppress("UNCHECKED_CAST")
    mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    null
  }

  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
}

拆分 ViewModel

随着业务的迭代,页面的逻辑变得复杂,这里的 ViewModel 类代码会变复杂,变得臃肿。

需要考虑进行拆分 ViewModel,这里直接上之前的一篇文章。

使用 Kotlin 委托,拆分比较复杂的 ViewModel - 掘金

相关推荐
Kapaseker2 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android