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 - 掘金

相关推荐
K1t01 分钟前
Android-UI设计
android·ui
吃汉堡吃到饱1 小时前
【Android】浅析MVC与MVP
android·mvc
深海呐8 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang8 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼8 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss9 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
一丝晨光10 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
消失的旧时光-194311 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男13 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽14 小时前
Android 源码集成可卸载 APP
android