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

相关推荐
&岁月不待人&1 小时前
实现弹窗随键盘上移居中
java·kotlin
用户2018792831674 小时前
如何利用AI工具快速学习Android源码
android
音视频牛哥5 小时前
Android 平台RTSP/RTMP播放器SDK接入说明
android·音视频·大牛直播sdk·rtsp播放器·rtmp播放器·rtmp低延迟播放·rtmpplayer
aningxiaoxixi6 小时前
Android Framework 之 AudioDeviceBroker
android·windows·ffmpeg
~Yogi6 小时前
今日学习:工程问题(场景题)
android·学习
奔跑吧 android7 小时前
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
android·bluetooth·bt·aosp13
移动开发者1号7 小时前
Android Activity状态保存方法
android·kotlin
移动开发者1号7 小时前
Volley源码深度分析与设计亮点
android·kotlin
张风捷特烈7 小时前
每日一题 Flutter#7,8 | 关于 State 两道简答题
android·flutter·面试
计蒙不吃鱼15 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端