Android面试题ViewModel源码分析,在什么情况下的「销毁重建」能够对数据进行无缝恢复?

Posted 小陈乱敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试题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()

准备好后便开始验证:

从上面视频可以看到,当手机横竖屏切换时,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之间

从源码里看它的作用就是将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,无法恢复数据。

android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub

文末

您的点赞收藏就是对我最大的鼓励!
欢迎关注我分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!

以上是关于Android面试题ViewModel源码分析,在什么情况下的「销毁重建」能够对数据进行无缝恢复?的主要内容,如果未能解决你的问题,请参考以下文章

2022年Android面试之Jetpack(AAC框架)篇

Android Jetpack之ViewModel源码分析

Android 面试之—— ViewModel 总结篇

Android异步消息传递机制源码分析&&相关知识常被问的面试题

Android arch components 源码分析—— ViewModel

80%的人都答不出来的 Android Framework面试题,整理给大家