Android ViewModel学习总结(源码级理解)
引言:为什么开发者需要ViewModel?
在Android开发中,屏幕旋转导致页面重建是开发者最头疼的问题之一。
传统方案通过onSaveInstanceState保存数据,但存在序列化性能瓶颈和数据量限制(Bundle最大1MB)。
而ViewModel的诞生,通过“生命周期穿透”能力,彻底解决了配置变更导致的数据丢失问题。
但它的实现原理究竟是什么?本文将揭开其底层三大黑科技。
一、HolderFragment:寄生在Activity中的“数据保险箱”
原理剖析
当调用ViewModelProviders.of(activity)时,系统会动态注入一个无UI的HolderFragment。这个Fragment的setRetainInstance(true)属性,使其在Activity重建时仍存活于内存中,其内部通过ViewModelStore对象缓存所有ViewModel实例。
源码级实现(以AndroidX 2.5.1为例):
// HolderFragment关键代码
publicclassHolderFragmentextendsFragment {
privateViewModelStoremViewModelStore=newViewModelStore();
@Override
publicvoidonCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); // 关键黑科技!
}
public ViewModelStore getViewModelStore() {
return mViewModelStore;
}
}
此设计让ViewModelStore与Activity生命周期解耦,实现跨配置变更的数据持久化。
扩展:
ViewModel生命周期穿透(破解内存泄漏困局)
高频面试题:
"为什么Fragment中获取的ViewModel会和Activity共享?如何实现作用域隔离?"
技术解析:
-
1. 源码级透视:
// 关键源码路径:androidx.fragment.app.FragmentViewModelLazy.kt
HolderFragment().apply {
fragmentManager.beginTransaction().add(this, HOLDER_TAG).commitNow()
}
- ViewModelProvider通过HolderFragment持有ViewModel实例
- FragmentManager通过FragmentTransaction将HolderFragment绑定到宿主的生命周期
-
2. 避坑指南:
- 错误用法:在onCleared()中调用context引发内存泄漏
- 正确方案:通过AndroidViewModel+ApplicationContext注入
ViewModel的"僵尸复活"机制(字节跳动压轴题)
场景还原:某电商App在屏幕旋转后购物车数据丢失,候选人无法解释ViewModel为何能存活
技术拆解:
- 底层原理:
-
ViewModel通过HolderFragment实现生命周期隔离(源码见
ViewModelStoreOwner
) -
数据存储依赖
onRetainNonConfigurationInstance()
方法实现跨配置保存
-
- 高频误区:
-
“ViewModel是单例模式”(错误率78%)
-
“ViewModel可以直接持有Context”(会导致内存泄漏)
-
- 解决方案:
// 正确获取ViewModel实例
val viewModel: MyViewModel by viewModels(
factoryProducer = { CustomFactory() }
)
避坑指南:
FragmentManager.findFragmentByTag("androidx.lifecycle.ViewModelProvider.Default")
可查看HolderFragment状态
二、ViewModelStoreOwner:多组件共享的“数据枢纽”
场景痛点
传统开发中,Activity与Fragment间的数据传递需要通过Bundle或接口回调,而ViewModel通过ViewModelStoreOwner接口,允许不同组件共享同一ViewModel实例。
实现技巧
- 通过扩展ViewModelProvider的owner参数,支持Activity/Fragment/Navigation图等不同作用域
- 在Fragment中调用ViewModelProviders.of(parentFragment),可获取父容器的ViewModel
案例演示:
// 在Fragment中获取Activity级别的ViewModel
val sharedModel: SharedViewModel by viewModels(requireActivity())
这种设计使得跨组件通信的代码量减少50%以上。
三、SavedStateHandle:进程级重建的“最后防线”
突破性能力
ViewModel默认只能抵御配置变更,但进程被系统杀死时数据仍会丢失。通过集成SavedStateHandle,可将数据写入系统管理的Bundle,实现跨进程销毁的数据恢复。
技术实现:
class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val liveData: MutableLiveData<String> = savedStateHandle.getLiveData("key")
init {
savedStateHandle.setSavedStateProvider("key") {
Bundle().apply { putString("key", liveData.value) }
}
}
}
底层通过AbstractSavedStateViewModelFactory,在ViewModel初始化时自动注入SavedStateHandle对象。
高频面试题深度解析
Q1:ViewModel与onSaveInstanceState有何本质区别?
答:
- 作用范围:ViewModel适用于大数据对象(如图片列表),而Bundle适合存储轻量级状态(如页面滚动位置)
- 生命周期:ViewModel存活到组件完全销毁(如Activity调用finish()),而Bundle仅在临时重建时有效
- 性能对比:ViewModel通过内存缓存避免序列化开销,性能提升约30倍
Q2:如何实现自定义生命周期的ViewModel?
进阶方案:
-
1. 继承ViewModel并重写onCleared()
-
2. 通过ViewModelProvider.Factory注入自定义作用域
-
3. 使用LifecycleObserver监听特定生命周期事件
public class CustomViewModel extends ViewModel {
@Override
protected void onCleared() {
// 释放资源
}
}
总结:ViewModel的三大应用场景
-
1. 跨屏幕旋转:HolderFragment + ViewModelStore机制
-
2. 跨组件通信:ViewModelStoreOwner的多级作用域控制
-
3. 跨进程恢复:SavedStateHandle与Bundle的深度集成
感谢观看!!!