One, foreword

This is a daily question from wanAndroid, just look at the ViewModel source code, do a solution. This article mainly involves the following three sub-questions:

  • Can the ViewModel restore data after a configuration change, such as rotation of the Activity?
  • If 1 can be analyzed from a source code perspective, where does the data exist? How is it stored? How did you read it?
  • When the Activity is switched to the background and is killed by the system (the process survives), can the ViewModel data be recovered when the Activity is restored? Why is that?

Two, case solution

First, let’s look at question 1: Can the ViewModel restore data when the Activity rotates?

Take chestnuts.

We create a ViewModel that internally manages an Int, and the user clicks a button on the interface to start incrementing, using LiveData to notify the UI of updates.

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

UI only has a TextView and a Button, TextView display data, Button number increment operation, LiveData to observe the data.

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() }Copy the code

Start validation when you are ready:

As you can see in the above video, when the phone switches between portrait and landscape, the Activity is rebuilt, but the data is not updated.

So for question 1, the obvious answer is that the ViewModel can recover data when its configuration changes, such as rotation of the Activity, occur.

Three, principle analysis

Section 2 starts with a case study to find the answer, but why does the ViewModel restore data and where does the data reside? How is it stored? How did you read it? It’s time to start exploring the source code.

First of all, let’s think about this. The purpose of the research is to understand why the data of the ViewModel is not cleared. The ViewModel is the place where the data is stored. So you can start by looking at how you get the ViewModel instance behind the portrait screen, which is how you create the ViewModel.

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

ViewModelProvider(this) instantiates the ViewModelProvider object, The second step, get(), is to take or create a viewModel instance directly from the viewModel class using the ViewModelProvider.

The key to creating a ViewModel is in the get() method.

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;
}
Copy the code

The ViewModel object is taken from the ViewModelStore according to the key. Then start comparing the ViewModel object with the ViewModel object passed in by the developer. If it is the same, return the ViewModel directly from the ViewModelStore. If not, recreate the ViewModel. And the new ViewModel object is stored in the ViewModelStore.

If there is any data in the cache, it will be returned. If there is no data in the cache, it will be recreated and put into the cache.

Here’s a class: ViewModelStore, which is the key to retrieving data from 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(); }}Copy the code

ViewModelStore is a class that stores ViewModels, and it actually implements a HashMap that holds ViewModel instances based on key. The ViewModel object is stored in the ViewModelStore during the reconstruction of the horizontal and portrait Activity on the phone, and is retrieved directly after the reconstruction. The instance of the ViewModel stays the same, and the data can be restored again.

Knowing that ViewModel objects are stored in ViewModelStore, where is the ViewModelStore created?

Remember that there are two steps in creating the ViewModel in the UI layer, and the first step is not analyzed by the ViewModelProvider(this), and the creation of the ViewModelStore is in this method, directly following where it was created.

#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 = newViewModelStore(); }}return mViewModelStore;
}

Copy the code

ViewModelStore is created in ComponentActivity. You can see a started in mViewModelStore is equal to null, is first obtained NonConfigurationInstances object.

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}
Copy the code

A class of NonConfigurationInstances is used to store ViewModelStore. When NonConfigurationInstances object is not null, directly from the first NonConfigurationInstances ViewModelStore, if can’t get the new a ViewModelStore object directly. So that’s how you get the ViewModelStore.

Here’s a NonConfigurationInstances, its object is by call getLastNonConfigurationInstance (), And getLastNonConfigurationInstance returned instance is returned by onRetainNonConfigurationInstance.

public final Object onRetainNonConfigurationInstance(a) {
    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;
}
Copy the code

The first point, onRetainNonConfigurationInstance trigger is a cell phone while it is somehow the screen, between onStop and onDestory.

Look at its role from the source code is stored in the viewModelStore NonConfigurationInstances. The viewModelStore is stored before the Activity is destroyed

Combined with the previous one, it all makes sense. Let’s take a look at how the viewModel stores and recovers data.

  1. When the ViewModel is first created, the ViewModelStore stores the newly created ViewModel objects using HashMap;

  2. In the mobile phone screen, before the Activity is destroyed, will trigger onRetainNonConfigurationInstance, storage for ViewModelStore;

  3. After the Activity is rebuilt, the Activity recycles the onCreate life cycle and again retrieves the ViewModel object. Instead of creating the ViewModel the first time, it will retrieve the ViewModelStore that the Activity saved before rebuilding through the ViewModelStoreOwner, and then in the ViewModelStore according to the Key, Find the ViewModel before the rebuild, and then restore the data.

Activity switch to the background, was killed by the system, ViewModel can restore data?

This problem is simulated in the form of a case. Section is the first example, only in the simulator set sets the background process to ‘do not allow the background process, when we will find, after the app back to the background did not trigger the onRetainNonConfigurationInstance at this time.

We can know from the above principle analysis, onRetainNonConfigurationInstance is the key to save the ViewModel before the Activity destroyed. Killed by the system without triggering it, the ViewModel cannot be saved, and the data cannot be recovered.

Recommended reading

Componentization +Jetpack+MVVM project combat

LiveData principle analysis