1. The Viewmode advantage

The ViewModel is designed to store and manage the data associated with the interface (in conjunction with the LiveData within it) in a life-cycle manner.

1.1 The UI processing and data processing of the Activity are separated, managed separately, decoupled and efficient.

1.2 ViewModel is retained after system configuration changes, such as screen rotation, to avoid wasting network resources by requesting data again. When the Activity is rebuilt, it receives the same ViewModel instance as the previous Activity held.

Only when the Activity is actually destroyed does the framework call getViewModelStore().clear() to clear all viewModels.

1.3 Avoid crash caused by refreshing the interface after the page is destroyed. For example, after the page initiates a request, the activity is closed before the data is returned. After the data is returned, the interface is refreshed and crash occurs because the View does not exist.

1.4 Both Fragments can use their Activity’s ViewModel to handle communication.

1.5 In contrast to onSaveInstanceState(), onSaveInstanceState() is suitable only for small amounts of data that can be serialized and deserialized, and not for potentially large amounts of data, such as user lists or bitmaps.

1.6 ViewModelScope defines a ViewModelScope for each ViewModel in the application. If the ViewModel is cleared, coroutines started in this range are automatically cancelled.

2. Pass the data to the ViewModel

// Create a ViewModel class with the argument class ViewModelDemo(var STR :String) : ViewModel() {var liveData = MutableLiveData<Int>(4)} // Create a ViewModel var viewModelDemo = with parameters through the factory class of the ViewModelProvider ViewModelProvider(this, object : ViewModelProvider.Factory { override fun <T : ViewModel? > create(modelClass: Class<T>): T { return ViewModelDemo("test") as T } }).get(ViewModelDemo::class.java)Copy the code

3. ViewModelProvider Obtains the ViewModel

3.1 ViewModelProvider Constructor

// By constructing the method call chain, Public ViewModelProvider(@nonNULL ViewModelStoreOwner owner) {this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } public class ComponentActivity extends androidx.core.app.ComponentActivity implements ContextAware, LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner, ActivityResultRegistryOwner, ActivityResultCallerCopy the code

ViewModelStoreOwner: is an interface that ComponentActivity and Fragment implement, so we can use this passed by the ViewModelProvider in the Activity or Fragment.

ViewModelStore: The ViewModelStore is mainly used to store ViewModel objects, and there is a HashMap collection used to store ViewModel objects.

ComponentActivity holds a ViewModelStore, which can be obtained via the getViewModelStore() method in ViewModelStoreOwner. (Why not get the ViewModelStore directly from the Activity and then get the ViewModel? Because the ViewModel to ViewModelProvider. Factory to create)

Factory: is an interface used to create viewModels

3.2 ViewModelProvider # the get ()

@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"); } // construct a key return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } @MainThread 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; } else { //noinspection StatementWithEmptyBody if (viewModel ! = null) { // TODO: log a warning. } } 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

After calling the get method, the second GET method is called, passing the key (DEFAULT_KEY + “:” + canonicalName) to the second GET method

We first get a ViewModel object from the ViewModelStore based on the supplied key

If the retrieved ViewModel object instance exists, it is returned

If the ViewModel object does not exist, a ViewModel object is created by Factory, stored in the ViewModelStore, and the newly created ViewModel object is returned.

There are three factories in the create method: Factory, KeyedFactory, and OnrequeryFactory. KeyedFactory and Factory have a key parameter.

When the ViewModelStore retrieves the ViewModel, it determines whether the current mFactory is of type OnRequeryFactory and calls back the onRequery method

So what’s the use of the OnRequeryFactory callback onRequery? In fact, the ViewModel can not only recover Activity data due to configuration changes, but also recover Activity data reclaimed due to system resource constraints, but the latter needs to rely on SaveStateHandler

Summary: The ViewModelProvider gets the ViewModel:

1. Create ViewModelProvider, pass in ViewModelStoreOwner and Factory 2, call the get method of ViewModelProvider, get the ViewModel from ViewModelStore, or return it directly. If not, return after creation.

4. Restore ViewModel

The ViewModel is retrieved from the ViewModelStore

ViewModelStore is through ViewModelStoreOwner getViewModelStore method

ComponentActivity ViewModelStoreOwner interface and HasDefaultViewModelProviderFactory is realized

# 4.1 ComponentActivity getViewModelStore ()

@Override 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."); } ensureViewModelStore(); return mViewModelStore; } void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}}Copy the code

GetViewModelStore () gets ViewModelStore in two ways

1, to get from the NonConfigurationInstances

2, New one out

NonConfigurationInstances, used for packaging is not affected by the configuration changes of data

The Activity of NonConfigurationInstances saved ViewModelStore when system configuration change and fragments could, etc

4.2 ActivityThread # handleRelaunchActivity

When system configuration changes, AMS calls the ActivityThread’s handleRelaunchActivity and builds an ActivityClientRecord from the ActivityRecord corresponding to the current Activity

The Activity lifecycle methods are executed in ActivityThread

private void handleRelaunchActivity(ActivityClientRecord tmp) { ... // Note that the last parameter getNonConfigInstance is true, HandleDestroyActivity (r.token, false, configChanges, true) if the Activity exits normally and goes to onDestory. . handleLaunchActivity(r, currentIntent, "handleRelaunchActivity"); } private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); }Copy the code

4.2 ActivityThread # performDestroyActivity

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { .... // Note that ActivityClientRecord is not removed from mActivities. Only after destroying will ActivityClientRecord be removed. performPauseActivityIfNeeded(r, "destroy"); if (! In this survey the Activity onStop() method callActivityOnStop(r, false /* saveState */, "destroy"); } / true, The normal exit to false if (getNonConfigInstance) {try {/ / call the corresponding retainNonConfigurationInstances method of the Activity / / the return value NonConfigurationInstance assign ActivityClientRecord lastNonConfigurationInstances hold within r.l astNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { if (! mInstrumentation.onException(r.activity, e)) { ... }}} / final callback to mInstrumentation onDestroy method. The callActivityOnDestroy (state Richard armitage ctivity); . }Copy the code

The Activity of retainNonConfigurationInstances calls onRetainNonConfigurationInstance

# 4.3 ComponentActivity onRetainNonConfigurationInstance ()

public final Object onRetainNonConfigurationInstance() { // Maintain backward compatibility. Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; If (viewModelStore == null) {// No one called getViewModelStore(), From lastNonConfigurationInstance viewModelStore NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; Create a NonConfigurationInstances} / /, Set the mViewModelStore at this time in NonConfigurationInstances nci = new NonConfigurationInstances (); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; }Copy the code

Before the call onDestory () method will create a NonConfigurationInstances object, will viewModelStore stored in NonConfigurationInstances, Then in ActivityClientrecord NonConfigurationInstances storage.

4.4 ActivityThread # performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... 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

ActivityThread handleLaunchActivity will call performLaunchActivity, eventually calls to the activity. The attach, the incoming lastNonConfigurationInstances

So for the new Activity, you get the NonConfigurationInstance of the previous Activity, and the ViewModelStore is the previous one, and so is the ViewModel.

This ensures that the ViewModel does not change when the system configuration changes.

Conclusion:

Create a NonConfigurationInstance when the system configuration changes. Keep the ViewModelStore as a NonConfigurationInstance. To save NonConfigurationInstance to ActivityClientrecord lastNonConfigurationInstances again

2, when the recovery, will ActivityClientrecord lastNonConfigurationInstances passed to a new Activity, Through getViewModelStore () to obtain the can get ViewModelStore from new Activity lastNonConfigurationInstances, then get ViewModel before

5. The Activity is destroyed normally

public ComponentActivity() { getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { // Clear out the available context mContextAwareHelper.clearAvailableContext(); // And clear the ViewModelStore if (! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); }Copy the code

When an Activity is normally destroyed, it cleans up all viewModels with getViewModelStore().clear().