ViewModel为什么可以实现数据保留与恢复?

数据保留的原理

ActivityFragment首次创建时,它们会请求一个ViewModel实例,通常是通过ViewModelProviderViewModelProvider负责实例化ViewModel对象,并确保在ActivityFragment的生命周期内这些对象保持不变。

  • 首次创建 :如果是首次创建,ViewModelProvider会创建一个新的ViewModel实例。
  • 配置更改后 :如果发生配置更改(如屏幕旋转),ActivityFragment将被销毁并重新创建。这时,ViewModelProvider会返回同一个ViewModel实例而不是创建一个新的实例。这就是ViewModel如何保持数据不丢失的关键所在。

ViewModel对象是存储在由ViewModelStore管理的一个容器中的,每个ActivityFragment都有自己的ViewModelStore。当ActivityFragment实例被销毁时,系统会检查是否是因为配置更改而销毁。如果是,ViewModelStore不会被清除,ViewModel就会继续存在。只有当ActivityFragment实例被最终销毁(例如用户完成了Activity),ViewModelStore才会被清除,随之ViewModel也会被清理。

源码流程解析

ViewModel的使用一般不能离开ViewModelProvider,我们以ViewModel的使用一般不能离开ViewModelProvider实例化为入口,看一下ViewModel是怎样运行的。

kotlin 复制代码
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,因此可以直接传入这个对象。

csharp 复制代码
public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwner里边返回了一个ViewModelStore,这个类里边,有一个hashMap,用来做数据存储。

typescript 复制代码
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处理的。

less 复制代码
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;
}
kotlin 复制代码
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(),最终会通过反射完成实例化:

typescript 复制代码
@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重新创建的时候,会触发 Activity#retainNonConfigurationInstances,

最终会进入:

ini 复制代码
# 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;
    }
}

这就是ViewModel可以用来做数据保存的底层原理。在ViewModel没有出来之前,我们可以自行重写 onRetainNonConfigurationInstance来实现数据的恢复。

包含关系:Activity或者Fragment包含ViewModelProvider,ViewModelProvider包含ViewModelStore,ViewModelStore包含ViewModel。

附:Acitivity与ViewModel的生命周期对应关系

只有在Acitivity被销毁之后,ViewModel才会回收。即便不可见也是存在的。

相关推荐
无双_Joney几秒前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥2 分钟前
前端必学的 CSS Grid 布局体系
前端·css
ccnocare4 分钟前
选择文件夹路径
前端
艾小码4 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月5 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁8 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅8 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸10 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端
我是日安10 分钟前
从零到一打造 Vue3 响应式系统 Day 9 - Effect:调度器实现与应用
前端·vue.js