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

相关推荐
sky北城12 分钟前
You are not able to choose some of the languages, because locales for them a
android
儿歌八万首18 分钟前
Jetpack Compose 实战:打造高性能轮播图 (Carousel) 组件
android·前端·kotlin
QING61822 分钟前
Kotlin Flow 防抖(Debounce)详解
android·kotlin·android jetpack
QING61834 分钟前
Kotlin Flow 防抖(Debounce)、节流(Throttle)、去重(distinctUntilChanged) —— 新手指南
android·kotlin·android jetpack
AI视觉网奇1 小时前
android yolo12 android 实战笔记
android·笔记·yolo
海上飞猪1 小时前
【Mysql】Mysql的安装部署和使用
android·mysql·adb
我是好小孩2 小时前
【Android】项目的组件化搭建
android
aqi002 小时前
FFmpeg开发笔记(九十四)基于Kotlin的国产开源推拉流框架anyRTC
android·ffmpeg·kotlin·音视频·直播·流媒体
马 孔 多 在下雨2 小时前
Android 组件化开发基础实践
android
技术摆渡人2 小时前
Android 系统技术探索(2)构建大脑(System Services & PMS)
android