美文网首页
ViewModel源码分析,在什么情况下的「销毁重建」能够对数据

ViewModel源码分析,在什么情况下的「销毁重建」能够对数据

作者: 付十一v | 来源:发表于2021-07-26 18:05 被阅读0次

    一、前言

    这个是来自wanandroid每日一问上的一个问题,刚好在看ViewModel的源码,就做一下解答。本篇文章主要涉及以下三个子问题:

    • ViewModel 在 Activity 发生旋转等配置发生变化所导致的重建,能恢复数据吗?
    • 如果 1 能,从源码角度分析,数据存在哪?怎么存储的?怎么读取的?
    • 当 Activity 切换到后台,被系统杀死(进程存活),此时回到 Activity 导致的重建,ViewModel 的数据能恢复吗?为什么?

    二、案例解答

    首先我们来看一下问题一:ViewModel在Activity发生旋转重建时,能恢复数据吗?

    举个栗子。

    我们创建一个ViewModel,内部管理一个Int型数据,并且用户在界面点击按钮就开始进行自增操作,使用LiveData通知UI更新数据。

    class ViewModelTest : ViewModel() {
        val countLiveData = MutableLiveData<Int>()
        var count: Int = 0
        fun addCount() {
            count++
            countLiveData.postValue(count)
        }
    }
    

    UI则只有一个TextView和一个Button,TextView展示数据,Button进行数字自增操作,LiveData对数据进行观察。

    val viewModel = ViewModelProvider(this).get(ViewModelTest::class.java)
    
    viewModel.countLiveData.observe(this, object : Observer<Int> {
        override fun onChanged(t: Int?) {
            text.text = t?.toString()
        }
    })
    
    findViewById<Button>(R.id.bt_add).setOnClickListener {
        viewModel.addCount()
    }
    

    准备好后便开始验证:

    [图片上传失败...(image-def543-1627293905906)]

    从上面视频可以看到,当手机横竖屏切换时,Activity会发生重建,但数据并没有重新更新。

    所以对于问题一,答案显而易见,ViewModel在Activity发生旋转等配置发生变化所导致的重建,可以恢复数据。

    三、原理解析

    第二节从案例出发找到了答案,但为什么ViewModel可以恢复数据,数据存在哪?怎么存储的?怎么读取的? 这就要开始去源码里一探究竟。

    首先我们想一下,研究的目的是为了了解ViewModel的数据为什么没有被清除掉,ViewModel是保存数据的地方,既然数据没变,那么必然在横竖屏切换时,ViewModel的实例也是没有变的。那么就可以从横竖屏后是怎么获取ViewModel实例的角度来入手,也就是ViewModel的创建。

    val viewModel = ViewModelProvider(this).get(ViewModelTest::class.java)
    

    ViewModel的创建很简单,一共两步,第一步 ViewModelProvider(this) 实例化了ViewModelProvider对象,第二步get() 就是利用该ViewModelProvider根据viewModel类直接拿或者创建ViewModel实例。

    创建ViewModel的重点在get()方法里,直接看:

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
    
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } 
        
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
    

    第一句就发现ViewModel对象是从ViewModelStore里根据key拿出来的。接着就开始拿着这个ViewModel对象和开发者传进的viewModel对象进行比较,如果一致,则就直接返回ViewModelStore里的viewModel,反之,则重新创建ViewModel,并且将新ViewModel对象存储到ViewModelStore中。

    看到这是不是有点缓存内味,先从缓存里拿数据,如果有数据就返回,如果缓存里没有就重新创建数据,并且将数据put到缓存中。

    这里有个类:ViewModelStore ,是ViewModel恢复数据的关键。

    public class ViewModelStore {
    
        private final HashMap<String, ViewModel> mMap = new HashMap<>();
    
        final void put(String key, ViewModel viewModel) {
            ViewModel oldViewModel = mMap.put(key, viewModel);
            if (oldViewModel != null) {
                oldViewModel.onCleared();
            }
        }
    
        final ViewModel get(String key) {
            return mMap.get(key);
        }
    
        Set<String> keys() {
            return new HashSet<>(mMap.keySet());
        }
    
        /**
         *  Clears internal storage and notifies ViewModels that they are no longer used.
         */
        public final void clear() {
            for (ViewModel vm : mMap.values()) {
                vm.clear();
            }
            mMap.clear();
        }
    }
    

    ViewModelStore是存储ViewModel的类,内部其实实现了一个HashMap,根据key拿ViewModel实例。在手机横竖屏Activity重建时,因为ViewModelStore之前存储了该ViewModel对象,重建后就直接取出来了。ViewModel的实例没变,数据也就可以再次恢复。

    了解了ViewModel对象是存储在ViewModelStore中,那ViewModelStore又是从哪里创建的?

    是否还记得在UI层创建ViewModel时分为两步,还有第一步ViewModelProvider(this)没有分析,而ViewModelStore的创建就是在这个方法中,直接跟到创建的地方。

    #ComponentActivity
    
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
    
    

    在ComponentActivity里实现了ViewModelStore的创建。可以看到一开始在mViewModelStore等于null时,则先获取了NonConfigurationInstances对象。

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
    

    NonConfigurationInstances是用来存储ViewModelStore的一个类。当NonConfigurationInstances对象不为null时,先直接从NonConfigurationInstancesViewModelStore,如果拿不到则就直接new一个ViewModelStore对象。这就是获取ViewModelStore的过程。

    这里有一个NonConfigurationInstances ,其对象是由调用getLastNonConfigurationInstance()获取,而getLastNonConfigurationInstance所返回的实例是由
    onRetainNonConfigurationInstance返回的。

    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
    
        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }
    
        if (viewModelStore == null && custom == null) {
            return null;
        }
    
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
    

    首先提一点,onRetainNonConfigurationInstance的触发是在手机横竖屏时,处于onStop和onDestory之间

    image.png

    从源码里看它的作用就是将viewModelStore存储到NonConfigurationInstances中。
    也就是在Activity销毁之前,就把viewModelStore存储起来了

    和前面相结合,一切都说得通了。我们再来捋一捋viewModel存储、恢复数据的过程。

    1. 第一次创建ViewModel时,ViewModelStore利用HashMap将新创建的ViewModel对象存储了起来;

    2. 在手机横竖屏时,Activity被销毁之前,会触发onRetainNonConfigurationInstance,对ViewModelStore进行存储;

    3. Activity重建后,Activity会重新走onCreate生命周期,并且会再次去获取ViewModel对象。而这次ViewModel的获取与第一次创建不同,它会通过ViewModelStoreOwner先获取该Activity重建之前所保存的ViewModelStore,接着在ViewModelStore中根据Key,找到重建之前的ViewModel,进而恢复数据。

    四、Activity切换到后台,被系统杀,ViewModel能否恢复数据?

    这个问题还是以案例的形式模拟一下。还是第一节的例子,只不过在模拟器设置里将后台进程设置为 ‘不允许后台进程’,当我们将app回到后台后发现,此时并没有触发onRetainNonConfigurationInstance。

    我们从上面的原理解析就知道,onRetainNonConfigurationInstance是Activity销毁前保存ViewModel的关键。被系统杀死,没有触发它,也就无法保存ViewModel,无法恢复数据。

    推荐阅读

    组件化+Jetpack+MVVM项目实战

    LiveData原理解析

    欢迎关注公 z 号:9点大前端,每天9点推荐更多前端、Android、Flutter文章

    相关文章

      网友评论

          本文标题:ViewModel源码分析,在什么情况下的「销毁重建」能够对数据

          本文链接:https://www.haomeiwen.com/subject/kaygmltx.html