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才会回收。即便不可见也是存在的。

相关推荐
吕彬-前端9 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱11 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai21 分钟前
uniapp
前端·javascript·vue.js·uni-app
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb