构造方法中初始化了Factory和ViewModelStore实例,继续往下走,跟下 get()
方法:
在此拼接了一个 key
,继续跟另一个 get()
方法:
这里执行的操作非常简单明了:
- ① 拼接key 区分不同作用域的ViewModel实例,规则:固定字符串+ViewModel完整类名;
- ② 尝试从 缓存Map 中根据key获取ViewModel实例;
- ③ 拿到:直接返回
- ④ 拿不到:往下走,通过工厂创建新ViewModel实例,保存到缓存Map中,然后返回;
到此好像还没get√到具体怎么实现作用域可控?
把关注点拉回 owner.getViewModelStore()
上,这个owner是 ViewModelStoreOwner
类型的,而我们上面传入的是 Activity实例
,可以推测Actitivty绝壁实现了这个接口。定位一波:
跟下 getViewModelStore()
2333,原来是 ComponentActivity
内部自己维护了一个ViewModelStore。
啧啧,再来看看Fragment又是怎么玩的,跟下 Fragment.getViewModelStore()
:
跟下 FragmentManager.getViewModelStore()
:
Fragment 内部持有一个 FragmentManagerViewModel
实例,点进去它的 getViewModelStore()
方法:
好家伙,我悟了:Fragment → FragmentManager → FragmentManagerViewModel → mViewModelStores集合
所以:ViewModel的作用域可控 = 工厂模式 + 缓存集合(特定key规则)。
关于ViewModel的特点大概了解到这,接着过下基本用法~
0x2、ViewModel 基本用法
老规矩,官方文档双手奉上:《ViewModel 概览》,以官方文档和源码为准~
① 依赖组件
ViewModel基本配合LiveData使用,更多依赖可以选择可参见:Lifecycle
def lifecycle_version = "2.4.1" // Java项目 implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // Kotlin项目 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
咳,如果你启用了DataBinding,可以不用另外依赖,不然会发现两个版本的ViewModel:
不信的话,自己命令行键入:gradlew :app:dependencies > dependencies.txt
扫一波就知道了
② 实现ViewModel
上面例子已经写得很明显了,就不重复了,这里提两点:
- 如果依赖了
activity-ktx
模块,可以使用by viewModels()
委托初始化ViewModel。
implementation 'androidx.activity:activity-ktx:1.4.0' val model: MyViewModel by viewModels() // Tips:如果依赖了fragment-ktx模块,可以在Fragment中商用activityViewModels() 委托初始化 // 宿主Activity的ViewModel implementation 'androidx.fragment:fragment-ktx:1.4.1' private val model: SharedViewModel by activityViewModels()
当所有者Activity关闭时,会调用ViewModel对象的onCleared() 方法,以便它可以清理资源。
- ViewModel绝不能引用View、Lifecycle或可能存储对Activity上下文的引用的类,内存泄露警告!!!
③ ViewModel的生命周期
ViewModel将一直存在与内存中,直到限定其时间访问的Lifecycle永久消失:对于Activity,是在Activity finish时,对于Fragment,是在Fragment移除时。基本用法就这些,协程搭配Jetpack组件使用,后面会专门讲~
0x3、面试题:ViewModel自动保存和恢复的原理
ViewModel好像就这个能问了,简单的探一探,使用 ViewModelProvider
实例化ViewModel时,传入 ViewModelStoreOwner
对象作为参数,Activity、Fragment自然实现了这个接口。跟下:ComponentActivity.getViewModelStore()
跟下 ensureViewModelStore
:
点开 NonConfigurationInstances
,可以看到 ViewModelStore 对象被缓存在这里:
跟下 getLastNonConfigurationInstance()
就是Activity除了提供 onSaveInstanceState()
和 onRestoreInstanceState()
外,还另外提供了两个方法 onRetainNonConfigurationInstance()
和 getLastNonConfigurationInstance()
专门处理配置更改。
跟下 onRetainNonConfigurationInstance()
:
就是在配置更改销毁重建过程中,先调用 onRetainNonConfigurationInstance()
保存 旧Activity中的ViewModelStore实例
,重建后通过 getLastNonConfigurationInstance()
获取到之前保存的ViewModelStore实例。
知道怎么保存和恢复,接着就是确定 调用时机 ,跟下 ActivityThread.performDestroyActivity()
它是 Activity销毁 调用的核心实现:
跟下 Activity.retainNonConfigurationInstances()
知道保存数据的方法是在这里调用的,接着看获取数据的方法又是在哪调用的,跟下 Activity.handleLaunchActivity()
,它是 Activity启动 的重要步骤:
还记得销毁处的代码吗:
销毁时,先存到 ActivityClientRecord.lastNonConfigurationInstances
中,然后在Activity启动时,通过 attach()
方法传递给新Activity。
到此就一清二楚了,onSaveInstanceState() 相关的也可以在 ActivityThread() 中找到踪迹,如:
就不去跟了,除了前面说的存数据的颗粒度大小不同外,两者还存在下述区别:
onSaveInstanceState()
的数据最终存储到ActivityManagerService
的ActivityRecord
中,即 系统进程,所以APP被杀后还能恢复;onRetainNonConfigurationInstance()
数据是存储到ActivityClientRecord
中,即 应用自身进程中 ,所以APP被杀后无法恢复。
另外,再送一个问题:ViewModelStore的onCleard()何时会被调用?
关于原理相关的就只了解到这吧,Activity销毁重建完整逻辑可是个大块头,就不展开讲了~
0x4、加餐:ViewModel-State组件
上面说过ViewModel仅对页面变更,Activity销毁后打开重建只能用onSaveInstanceState(),写个简单例子验证下:
class VMFirstActivity: AppCompatActivity() { companion object { const val COUNT_TAG = "count" } private var mSaveInstanceCount = 0 private val mModel: VMViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_wm_first) // 判断是否有保存的数据,有取值 savedInstanceState?.let { mSaveInstanceCount = it.getInt(COUNT_TAG) } tv_vm_content.text = "ViewModel保存的数据:${mModel.mCount}" tv_on_save_content.text = "onSaveInstanceState()保存的数据:${mSaveInstanceCount}" bt_test.setOnClickListener { tv_vm_content.text = "ViewModel保存的数据:${++mModel.mCount}" tv_on_save_content.text = "onSaveInstanceState()保存的数据:${++mSaveInstanceCount}" } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt(COUNT_TAG, mSaveInstanceCount) } } class VMViewModel: ViewModel() { var mCount = 0 }
运行后点击按钮自增多次,然后旋转手机引起Activity销毁重建,效果一致:
接着试下杀掉进程,home键退到后台,键入下述命令:
adb shell am kill 应用包名
点击桌面图标重新打开:
果然,ViewModel中的数据丢失了,如果数据比较重要,而且量不大,可以在onCreate()拿到savedInstanceState时也重置一下值。
savedInstanceState?.let { mSaveInstanceCount = it.getInt(COUNT_TAG) mModel.mCount = it.getInt(COUNT_TAG) }
然后就可以了,当然,这样搞法有点冗余,如果能判断是配置变更引起的重建,还是异常销毁引起的重建就好了,笔者暂时没找到判定的API,只能这样了。有知道的小伙伴欢迎在评论区告知~
对于这种场景,Jetpack其实还给我们提供了一个模块:SaveState
,activity库内部默认引入了这个组件,不需要另外依赖,当然你要依赖特定版本也是可以的:
implementation "androidx.savedstate:savedstate:1.0.0"
用法非常简单,ViewModel的构造方法,传入一个SavedStateHandle参数,然后用这个参数读取数据即可:
class VMViewModel(private val state: SavedStateHandle): ViewModel() { private val countTag = "count" fun setValue(value: Int) = state.set(countTag, value) fun getValue() = state.get<Int>(countTag) }
把原先onSaveInstanceState相关的代码干掉后:
class VMFirstActivity: AppCompatActivity() { private val mModel: VMViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_wm_first) // 如果为空,要先设置一个初始值,不然一直都是null if(mModel.getValue() == null) mModel.setValue(0) tv_vm_content.text = "ViewModel保存的数据:${mModel.getValue()}" bt_test.setOnClickListener { // 需要判空,然后在更新值 mModel.getValue()?.plus(1)?.let { mModel.setValue(it) } tv_vm_content.text = "ViewModel保存的数据:${mModel.getValue()}" } } }
清爽多了,除了可以调用 get()
方法外,还可以调用 getLiveData()
来获取LiveData类型的值。代码就更精简了:
非常简单,不过要注意下:
存数据跟Bundle一样,存对象要序列化,然后也适合保存轻量级的数据!!!
原理的话,还是利用的 onSaveInstanceState(),每个ViewModel的数据单独存在一个Bundle中,再合并成一个整体,放到outBundle,所以它同样不能存超过1M的数据。
具体源码和流程就不去刨了,感兴趣的可自行查阅下述两篇文章:
0x5、小结
本节过了下ViewModel的用法,对它的特点:视图数据与控制器、数据管理的一致性、数据共享、作用域可控进行了详解的解读,并配以简单例子帮助理解,还从源码层面讲解了ViewModel自动保存和恢复的原理,最后还提了一嘴ViewModel-State组件的使用。基本上算是面面俱到了,相信看完的读者用起ViewModel来也是水到渠成了。
参考文献:
- 官方文档:ViewModel 概览
- 重学安卓:在页面开发中 左右逢源的 Jetpack ViewModel
- Android 面试总结 - ViewModel 是怎么保存和恢复?
- ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别
- Android官方架构组件ViewModel:从前世今生到追本溯源