1. What is ViewModel

A ViewModel is a data store component that has post-life awareness of the host. It can be understood that the ViewModel can be used to store data and still exist when an Activity is recreated due to exception destruction. This is similar to onInstanceSave and onRestoreInstance for an Activity. ViewModel data can be shared between activities and fragments.

2, ViewModel simple use

Import dependence

Implementation 'androidx. Lifecycle: lifecycle - viewmodel: 2.2.0'Copy the code

For the ViewModel, just learn a few of its features first. 1. You can share data in activities and Fragments. 2. You can share data across multiple activities. 3. Data can be saved during the life cycle of the exception.

Get the ViewModel in the code

Var myViewModel = ViewModelProvider(this).get(myViewModel ::class.java) // myViewModel = ViewModelProvider(viewModelStore,ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)Copy the code

Either way is fine. After initialization, you can use the data in the ViewModel.

Different initialization methods for sharing data between activities and fragments are used in activities:

var myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
Copy the code

This stands for activity or application, and if it is application, then the viewModel can be shared among multiple activities.

The ViewModel is relatively simple to use, and I’ll write the sample code at the end. Usually used in conjunction with LiveData.

3, ViewModel source code analysis

Let’s start with the get method that gets the ViewModel.

@MainThread public <T extends ViewModel> T get(@NonNull String key, @nonnull Class<T> modelClass) {// mViewModelStore can be thought of as a map to store VieModel, ViewMode ViewModel ViewModel = mViewModelStore.get(key) If (modelclass.isinstance (viewModel)) {// If (modelclass.isinstance (viewModel)) { If (mFactory instanceof OnRequeryFactory) {((OnRequeryFactory) mFactory).onRequery(viewModel); } return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel ! = null) { // TODO: Log a warning.}} // The ViewModel is not available, 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

With this code, we need to know 1. 2, how to create viewModel;

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
Copy the code

As you can see from this method, 1, there will be a DEFAULT_KEY+ ViewModle class name. 2. Call get(String Key,Class modelClass) directly to define a key.

Let’s look at how to create this ViewModel:

public interface Factory { /** * Creates a new instance of the given {@code Class}. * <p> * * @param modelClass a {@code  Class} whose instance is requested * @param <T> The type parameter for the ViewModel. * @return a newly created ViewModel */ @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass); }Copy the code

You can see the create method in the GET method. It is in a Factory interface, which, as the name suggests, is actually a Factory mode.

There are so many implementation classes, so which one to use?

Back to initializing the ViewModelProvider method

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

Copy the code

As you can see, several factories are generated by default. The source code contains two Factory implementations. NewInstanceFactory and AndroidViewModelFactory, you can just look at the NewInstanceFactory

@Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { //noinspection TryWithIdenticalCatches try { return modelClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); }}Copy the code

The NewInstanceFactory method instantiates the object directly through class. NewInstance.

After the initial initialization, we started to solve the following questions: 1. Why can this object share data between activities and fragments? Initialize the ViewModelProvider method.

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

Copy the code

It actually calls:

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
Copy the code

The mViewModelStore thing is passed in from the Activity. So it’s unique in the Activity. As you can see from the previous get method, once the ViewModel is created it will be stored in the mViewModelStore and can be retrieved directly from the mViewModelStore next time. So if the Fragment needs to share the data in the Activity, it just needs to pass in the class of the Activity and the same ViewModel at initialization.

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        var viewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
    }
Copy the code

MyViewModel:: Class. Java will also get the existing ViewModel from mViewModelStore, and naturally can achieve data sharing.

2. Why does this thing share data across multiple activities? The ViewModel is synchronized with the Application lifecycle. All activities can access the ViewModel and share the data in it. 1. AndroidViewModelFactory. You can share data across multiple activities using AndroidViewModelFactory.

2. Application implements ViewModelStoreOwner, and can also share data among multiple activities.

class BaseApplicaiton : Application(), ViewModelStoreOwner {

    companion object{
        var instance:BaseApplicaiton? = null
    }

    private val appViewModelStore: ViewModelStore by lazy {
        ViewModelStore()
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }
}
Copy the code

How does the ViewModel recover data during the Activity exception lifecycle?

To save data, look at the performDestoryActivity method for ActivityThread:

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r ! = null) { activityClass = r.activity.getClass(); r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; } performPauseActivityIfNeeded(r, "destroy"); if (! r.stopped) { callActivityOnStop(r, false /* saveState */, "destroy"); } / / specific see if this code (getNonConfigInstance) {try {r.l astNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { if (! mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to retain activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); }}} return r; }Copy the code

RetainNonConfigurationInstances this method is to some of the parameters in the save configuration to NonConfigurationInstances Activity.

NonConfigurationInstances retainNonConfigurationInstances() { Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); // omit some code return nci; }Copy the code

OnRetainNonConfigurationInstance: here is mainly to save viewModelStore to NonConfigurationInstances Activity.

public final Object onRetainNonConfigurationInstance() { // Maintain backward compatibility. 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

Continue to trace source can see save data NonConfigurationInstances getLastNonConfigurationInstance method.

    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
Copy the code
    static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }
Copy the code

An Activity has an ActivityClientRecord, which is a collection of parameters that record the Activity. In the Activity. The NonConfigurationInstances lastNonConfigurationInstances; Can see from the above code, when the Activity destroyed here gave the Activity of NonConfigurationInstances assignment ActivityClientRecord NonConfigurationInstances.

To restore the data, see ActivityThread’s performLaunchActivity method:

            
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
Copy the code

Here will lastNonConfigurationInstances into, here called the activity of the attach method. In this way will ActivityClientRecord NonConfigurationInstances NonConfigurationInstances assigned to the Activity.

4. ViewModel vs. onSaveInstance(

1. OnSaveInstance stores data via Bundle. It stores serializable data, usually 1M-8K in size. The ViewModel can hold data in any form, no more than the amount of memory allocated by the system’s App. 2. OnSaveInstance stores the data on disk, whereas ViewModel stores the data in memory.

5, summary

1. ViewModel: ViewModel 2. Data sharing principle of ViewModel. 3. How the ViewModel can save data during the Activity exception life cycle. 4. Some differences between ViewModel and onSaveInstance