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

相关推荐
慧一居士12 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead14 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
伍哥的传说8 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409198 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app