ViewModel初始化 java viewmodel_recoil_数据

一、前言

本篇文章主要涉及以下三个问题:

  • 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()
}

准备好后便开始验证:

ViewModel初始化 java viewmodel_recoil_ViewModel初始化 java_02

ViewModel初始化 java viewmodel_recoil_数据_03

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

ViewModel初始化 java viewmodel_recoil_数据_04

从源码里看它的作用就是将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