ViewModel
前言
选读书籍--《Android Jetpack开发 原理解析与应用实战》------ 著: 黄林晴
本篇主要根据选读书籍来学习ViewModel组件,而并非学习MVVM框架
时间: 23/09/12
AndroidStudio
版本: Giraffe 2022.3.1 JDK:17 开发语言: Kotlin
Gradle版本: 8.0 Gradle plugin Version: 8.1.1
ViewModel管理数据
旋转屏幕导致数据重新赋值
在实际开发过程中,总会有需要手机切换横竖屏的需求,但是在不设置configChanges的情况下,横竖屏切换默认都会导致Activity界面重新绘制,包括重新执行onCreate()等周期方法。
那么这个时候,屏幕中保存的数据就会被刷新回初始状态。
kotlin
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
lateinit var binding: ActivityMainBinding
private var showInt = 5
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.show.text = showInt.toString()
Log.d(TAG, "show number, now is $showInt")
binding.add.setOnClickListener {
showInt++
binding.show.text = showInt.toString()
Log.d(TAG, "add number, now is $showInt")
}
}
}
Log显示
一般情况下,我们会使用到onCreate()方法中的Bundle来存储、获取界面中的数据,这种方法这里暂不做详细描述。
还有一种方式就是使用ViewModel来存放数据。
使用ViewModel
-
引入依赖
scssimplementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
-
新建一个MainViewModel类继承
ViewModel
kotlinclass MainViewModel : ViewModel() { var showInt = 5 }
-
在MainActivity中初始化MainViewModel,并替换
showInt
kotlinclass MainActivity : AppCompatActivity() { private val TAG = "MainActivity" lateinit var binding: ActivityMainBinding private lateinit var mainViewModel: MainViewModel private var showInt = 5 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] binding.show.text = mainViewModel.showInt.toString() Log.d(TAG, "show number, now is ${mainViewModel.showInt}") binding.add.setOnClickListener { mainViewModel.showInt++ binding.show.text = mainViewModel.showInt.toString() Log.d(TAG, "add number, now is ${mainViewModel.showInt}") } } }
Log显示
原理
既然每次横竖屏切换都会执行一遍onCreate,那就说明每次都重新初始化了一遍ViewModel,那为什么ViewModel中的值不会发生变化呢?
查看初始化操作代码的源码------ViewModelProvider
中的get()方法
kotlin
@MainThread
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)
}
kotlin
@MainThread
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
} else {
@Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) {
// TODO: log a warning.
}
}
val extras = MutableCreationExtras(defaultCreationExtras)
extras[VIEW_MODEL_KEY] = key
// AGP has some desugaring issues associated with compileOnly dependencies so we need to
// fall back to the other create method to keep from crashing.
return try {
factory.create(modelClass, extras)
} catch (e: AbstractMethodError) {
factory.create(modelClass)
}.also { store.put(key, it) }
}
很容易理解哈,我们从上往下看
- 先通过key从store中拿出来一个viewModel,这就可以看出来这个store是存放
ViewModel
的。 - 拿到这个viewModel之后,判断它是不是就是
modelClass
对应的那个ViewModel
,如果是直接返回这个viewModel - 如果不符合上述情况,就走到下面通过
factory
,即工厂模式来创建一个新的ViewModel,并在最后添加到store
中,然后返回这个ViewModel。
注意:从上述源码可以看出,ViewModel的生命周期会比Activity长很多,因此如果ViewModel直接引用View(例如:Context) 的话会导致内存泄漏
ViewModel其它应用
通过上面的实践和分析,我们知道ViewModel
中的信息可以得到暂时的保存,那么我们可以通过这个特性,来优化代码。
例如我们可以在一个Activity,多个fragment之间实现 fragment之间的"共享"数据。实际上就是在Activity中初始化ViewModel,在多个fragment中去调用同一个ViewModel,通过这个ViewModel中的方法获取或写入数据实现共享。这种方式就类似于对文件的读写,不同的是这个数据仍然是无法长久保存的。