数据保留的原理
ViewModel 能实现数据保留与恢复,关键就在于它的存储位置和生命周期绑定机制。
ViewModel 不是和 Activity/Fragment 绑定的,而是和 ViewModelStore 绑定。ViewModelStore 能跨配置变更(比如旋转屏幕)自动保存和恢复 ViewModel 实例,所以能做到“短暂销毁不丢数据”。
当Activity或Fragment首次创建时,它们会请求一个ViewModel实例,通常是通过ViewModelProvider。ViewModelProvider负责实例化ViewModel对象,并确保在Activity或Fragment的生命周期内这些对象保持不变。
- 首次创建:如果是首次创建,
ViewModelProvider会创建一个新的ViewModel实例。 - 配置更改后:如果发生配置更改(如屏幕旋转),
Activity或Fragment将被销毁并重新创建。这时,ViewModelProvider会返回同一个ViewModel实例而不是创建一个新的实例。这就是ViewModel如何保持数据不丢失的关键所在。
ViewModel对象是存储在由ViewModelStore管理的一个容器中的,每个Activity或Fragment都有自己的ViewModelStore。当Activity或Fragment实例被销毁时,系统会检查是否是因为配置更改而销毁。如果是,ViewModelStore不会被清除,ViewModel就会继续存在。只有当Activity或Fragment实例被最终销毁(例如用户完成了Activity),ViewModelStore才会被清除,随之ViewModel也会被清理。
原理拆解
-
存储位置不同
- ViewModel 并不是 Activity 或 Fragment 的普通成员变量。
- 它存放在一个叫做
ViewModelStore的专用对象里(Activity/Fragment 都有自己的 ViewModelStore)。
-
生命周期“更长”
- Activity 或 Fragment 一旦因为配置变更(如屏幕旋转、深色模式切换)被系统销毁并重建,普通变量会丢失。
- 但系统底层会把 ViewModelStore 保留(用
onRetainNonConfigurationInstance技术),ViewModel 依然留在内存,不会被销毁。
-
自动恢复机制
- 新创建的 Activity/Fragment 会复用原来的 ViewModelStore,里面的 ViewModel 也自动“复活”了。
- 所以数据自动恢复,不需要你手动保存/恢复。
-
彻底销毁才会清理
- 只有 Activity/Fragment 被最终彻底销毁(如用户退出页面、进程被杀),ViewModelStore 才被清空,ViewModel 才 onCleared。
源码流程解析
ViewModel的使用一般不能离开ViewModelProvider,我们以ViewModel的使用一般不能离开ViewModelProvider实例化为入口,看一下ViewModel是怎样运行的。
class MainActivity : AppCompatActivity() {
// 300个字段
// var number : Int = 0;
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("DDD", "onCreate: ")
// myViewModel = MyViewModel() 不能直接实例化,因为如果能这样写,无法对生命周期进行管控。
// 旧版本的写法,更新特别快(扩展性不强)
// ViewModelProviders.of()
// this == ViewModelStoreOwner,
myViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
.get(MyViewModel::class.java)
Activity实现了ViewModelStoreOwner,因此可以直接传入这个对象。
public interface ViewModelStoreOwner {
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelStoreOwner里边返回了一个ViewModelStore,这个类里边,有一个hashMap,用来做数据存储。
public class ViewModelStore {
// 用map来存储ViewModel,因为一个页面是很有可能有多个ViewModel的。
private final HashMap<String, ViewModel> mMap = new HashMap();
public ViewModelStore() {
}
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = (ViewModel)this.mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return (ViewModel)this.mMap.get(key);
}
Set<String> keys() {
return new HashSet(this.mMap.keySet());
}
public final void clear() {
Iterator var1 = this.mMap.values().iterator();
while(var1.hasNext()) {
ViewModel vm = (ViewModel)var1.next();
vm.clear();
}
this.mMap.clear();
}
}
然后来看看 ViewModelProvider 的实例化的第二个参数,采用的 Factory 来实现.
ViewModel 的实例化过程中,会调用 owner.getViewModelStore(),这个的实现是在ComponentActivity处理的。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
this.mFactory = factory;
// 这就是我们最终的成果。保存这个成员变量。
this.mViewModelStore = store;
}
public ViewModelStore getViewModelStore() {
if (this.getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
} else {
// 构建一个ViewModelStore。
if (this.mViewModelStore == null) {
// 获取上一次状态的存储类的数据
NonConfigurationInstances nc = (NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null) {
this.mViewModelStore = nc.viewModelStore;
}
if (this.mViewModelStore == null) {
this.mViewModelStore = new ViewModelStore();
}
}
return this.mViewModelStore;
}
}
当然,在Fragment中也有类似的实现。
.get(MyViewModel::class.java) 调用工厂ViewModelProvider.NewInstanceFactory(),最终会通过反射完成实例化:
@NonNull
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return (ViewModel)modelClass.newInstance();
} catch (InstantiationException var3) {
throw new RuntimeException("Cannot create an instance of " + modelClass, var3);
} catch (IllegalAccessException var4) {
throw new RuntimeException("Cannot create an instance of " + modelClass, var4);
}
}
当Activity重新创建的时候,
- 系统会调用
retainNonConfigurationInstances(),它会进一步调用你的onRetainNonConfigurationInstance()(如果你有重写的话),并把自定义对象和 ViewModelStore 等一起打包,临时保存下来。
# public final Object onRetainNonConfigurationInstance() {
Object custom = this.onRetainCustomNonConfigurationInstance();
// 如果mViewModelStore不为空的话,就会通过此来进行数据的恢复。
ViewModelStore viewModelStore = this.mViewModelStore;
NonConfigurationInstances nci;
if (viewModelStore == null) {
// 获取上一次状态的存储类的数据
nci = (NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nci != null) {
viewModelStore = nci.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
} else {
// 将这些数据存储到当前的nci中,以便下次配置变化时使用。
nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
}
- 新的 Activity 创建时,通过
getLastNonConfigurationInstance()拿到这些数据,实现“数据穿越”。 最终会进入:
这就是ViewModel可以用来做数据保存的底层原理。在ViewModel没有出来之前,我们可以自行重写 onRetainNonConfigurationInstance来实现数据的恢复。
包含关系:Activity或者Fragment包含 ViewModelProvider,ViewModelProvider 包含ViewModelStore ,ViewModelStore 包含 ViewModel 。
附:Acitivity与ViewModel的生命周期对应关系
只有在Acitivity被销毁之后,ViewModel才会回收。即便不可见也是存在的。
时序图梳理
配置变更(旋转):
──> Activity.onDestroy()
│
└─> 系统把 ViewModelStore 临时保存
│
新 Activity.onCreate()
│
拿到之前保存的 ViewModelStore
│
旧的 ViewModel 直接复用
彻底销毁:
──> Activity.onDestroy()
│
└─> ViewModelStore 被清理
│
ViewModel.onCleared() 被调用,内存释放
只有彻底销毁时(比如按返回键退出页面)才会真正释放 ViewModel。配置变更时 ViewModelStore 不会被清理,所以能复用 ViewModel 实例。你看到的“销毁-重建”其实是 ViewModelStore 的“移交”而不是“清空”。
学后测验
1. 下列关于 Android ViewModel 的说法,正确的是?
A. ViewModel 只能在 Activity 被销毁后才会被回收
B. ViewModel 数据在配置更改(如旋转)时会丢失
C. ViewModel 只能被一个 Fragment 共享
D. ViewModelProvider 负责 ViewModel 的实例化和生命周期管理
答案:D
解析:
A错,ViewModel 在 Activity/Fragment 销毁时才被回收;B错,配置变更不会丢失数据;C错,可用于多个 Fragment 共享同一 ViewModel(通过同一个 ViewModelStoreOwner);D对,ViewModelProvider 管理实例化及生命周期。
2. 下列哪个类实际负责存储 ViewModel 实例?
A. ViewModelProvider
B. ViewModelStore
C. ViewModelFactory
D. ViewModelStoreOwner
答案:B
解析:ViewModelStore 内部有 HashMap,真正存储 ViewModel 实例;Provider 是入口,Factory 用于创建,StoreOwner 持有 Store。
3. ViewModelProvider 获取 ViewModel 实例时,第二个参数 Factory 的主要作用是什么?
A. 指定 ViewModel 的生命周期
B. 控制 ViewModel 的回收时机
C. 控制 ViewModel 的创建方式
D. 存储 ViewModel 实例
答案:C
解析:Factory 用于自定义 ViewModel 的创建(如带参数构造等),不是生命周期或存储。
4. 配置变更(如屏幕旋转)时,ViewModel 不会被销毁,是因为以下哪项机制?
A. Application 单例模式
B. onRetainNonConfigurationInstance() 方法
C. Handler 机制
D. LiveData 机制
答案:B
解析:本质上 ViewModelStore 利用了 onRetainNonConfigurationInstance() 保存和恢复 ViewModelStore 实例,实现跨配置变更数据不丢失。
5. Activity 作为 ViewModelStoreOwner,实际提供 ViewModelStore 的逻辑位于哪个方法?
A. onCreate()
B. getViewModelStore()
C. onDestroy()
D. onSaveInstanceState()
答案:B
解析:Activity 通过实现 getViewModelStore() 提供 ViewModelStore 实例。
填空题
6. ViewModelProvider 持有的两个关键成员变量是 _______ 和 _______。
答案:ViewModelStore, Factory
解析:ViewModelProvider 需要这两者来实现获取和创建 ViewModel。
7. ViewModelStore 内部使用 _______ 数据结构来存储多个 ViewModel 实例。
答案:HashMap
解析:源码就是 private final HashMap<String, ViewModel>。
8. 当 Activity 被真正销毁(如按返回键退出),ViewModelStore 的 _______ 方法会被调用,清理所有 ViewModel。
答案:clear
解析:只有在 Activity 最终销毁才会调用 clear() 清理 ViewModel。
简答题
9. 简述为什么 ViewModel 不能直接通过 new 方式创建,而必须通过 ViewModelProvider 获取?
答案示例:
因为直接 new 出来的 ViewModel 无法被系统纳入生命周期管理,也不能实现配置变更时的数据保留。ViewModelProvider 会自动管理实例复用、销毁和恢复,保证 ViewModel 在合适的生命周期内有效,并且避免出现多个重复 ViewModel 实例。
10. 画出以下对象之间的关系链:Activity(或 Fragment)— ViewModelProvider — ViewModelStore — ViewModel。
答案参考:
Activity/Fragment
│implements
↓
ViewModelStoreOwner
│getViewModelStore()
↓
ViewModelStore
│HashMap<String, ViewModel>
↓
ViewModel
编程/伪代码题
11. 写出自定义 ViewModelFactory 用于创建带参数构造函数的 ViewModel 的基本代码结构。
答案参考:
class MyViewModel(val param: String) : ViewModel() {
// ...
}
class MyViewModelFactory(private val param: String) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
return MyViewModel(param) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// 用法
val factory = MyViewModelFactory("参数")
val vm = ViewModelProvider(this, factory).get(MyViewModel::class.java)