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

相关推荐
gongzemin5 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox18 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周2 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端