It’s official: The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. It has two characteristics:

  • The ViewModel class allows data to persist after configuration changes such as screen rotation
  • Bind to the component lifecycle such as Activity

use

class ProfileViewModel : ViewModel(){
}

class ProfileActivity : FragmentActivity() {

    private lateinit var profileViewModel: ProfileViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initData()
    }
    
    private fun initData() {
        profileViewModel = ViewModelProvider(this).get(ProfileViewModel::class.java)
    }
Copy the code

Source code analysis

Basic function analysis

1. How does the ViewModel class make data persist after configuration changes such as screen rotation

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

Its constructor takes a ViewModelStoreOwner argument. If you look at ViewModelStoreOwner, you can see that ViewModelStoreOwner is an interface that defines only one function:

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}
Copy the code

In the example above, pass this(FragmentActivity) as an argument. FragmentActivity inherits ComponentActivity, which implements the ViewModelStoreOwner interface.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {
}
Copy the code

The ViewModelProvider constructor calls its own constructor with two arguments, mainly to assign values to mFactory and mViewModelStore:

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

ViewModelStoreOwner ComponentActivity in addition to the implementation, also implements the HasDefaultViewModelProviderFactory interface, So mFactory will use it by getDefaultViewModelProviderFactory () value:

@NonNull @Override public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { 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 (mDefaultFactory == null) { mDefaultFactory = new SavedStateViewModelFactory( getApplication(), this, getIntent() ! = null ? getIntent().getExtras() : null); } return mDefaultFactory; }Copy the code

There is one important class: SavedStateViewModelFactory, here not to analysis. To go back to our use example, look at the viewModelProvider.get () function:

@SuppressWarnings("unchecked") @NonNull @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

The ViewModel is obtained mainly through the mViewModelStore, and there are two cases:

  • The first time I get it,mViewModelStore.get(key)The obtained viewModel must be empty, continue, and mFactory is assigned toSavedStateViewModelFactory. whileSavedStateViewModelFactoryIs inherited fromViewModelProvider.KeyedFactorySo it goes toviewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);. The ViewModel object is created with the specified Factory. I’m going to let that go, and then I’m going to sumSavedStateViewModelFactoryAnalyze it together.
  • ifmViewModelStore.get(key)If the obtained viewModel is not empty, it is returned directly. If you want to make configuration changes to continue to retain data, you must ensuremViewModelStoreIt’s consistent! While mViewModelStore is provided by ComponentActivity, let’s go back to its getViewModelStore:
@NonNull @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."); } 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; }Copy the code

If is empty, first by getLastNonConfigurationInstance () to obtain:

Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate and onStart calls to the new instance, allowing you to extract any useful dynamic state from the previous instance. Note that the data you retrieve here should  only be used as an optimization for handling configuration changes. You should always be able to handle getting a null pointer back, and an activity must still be able to restore itself to its previous state (through the normal onSaveInstanceState(Bundle) mechanism) even if this function returns null. Note: For most cases you should use the Fragment API Fragment.setRetainInstance(boolean) instead; this is also available on older platforms through the Android support libraries. Returns: the object previously returned by onRetainNonConfigurationInstance() @Nullable public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances ! = null ? mLastNonConfigurationInstances.activity : null; } Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance. If you are targeting Build.VERSION_CODES.HONEYCOMB or later, consider instead using a Fragment with Fragment.setRetainInstance(boolean. This function is called purely as an optimization, and you must not rely on it being called. When it is called, a number of guarantees will be made to help optimize configuration switching: The function will be called between onStop and onDestroy. A new instance of the activity will always be immediately created after this one's onDestroy() is called. In particular, no messages will be dispatched during this time (when the returned object does not have an activity to be associated with). The object you return here will always be available from the getLastNonConfigurationInstance() method of the following activity instance as described there. public Object onRetainNonConfigurationInstance() { return null; }Copy the code

There are two key functions, and we’ll take a look at the API description:

  • OnRetainNonConfigurationInstance: when activity destroyed due to configuration changes, the system will automatically call the function. Can be used to save data that we want to recover when the activity is restarted.
  • GetLastNonConfigurationInstance: for we are onRetainNonConfigurationInstance saved data information

OnRetainNonConfigurationInstance defined in the Activity, we see this function in ComponentActivity rewrite:

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

Here you can see, the whole mViewModelStore packaged into NonConfigurationInstances, and then return. You can also parse feature one here: the ViewModel class allows data to persist after configuration changes such as screen rotation.

2. Binding to the component lifecycle

How is it tied to the component’s life cycle? Next, we need to distinguish between activities and fragments

  • Activity

Look directly at the code:

public ComponentActivity() { getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); }Copy the code

LifecycleEventObserver directly calls getViewModelStore().clear() when Lifecycle.event.on_destroy; . IsChangingConfigurations is also determined because you need to restore the data provided by the ViewProvider when the activity is restarted due to configuration, so you cannot clear it.

  • Fragment
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
        ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner {
    
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }        
    
    @NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    requireActivity().getApplication(),
                    this,
                    getArguments());
        }
        return mDefaultFactory;
    }    
}
Copy the code

Fragments also inherited ViewModelStoreOwner and HasDefaultViewModelProviderFactory two interfaces, Can see its getViewModelStore is called the mFragmentManager getViewModelStore (this), we continue to look inside:

// FragmentManager.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    return mNonConfig.getViewModelStore(f);
}

// FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}
Copy the code

Finally is to call the FragmentManagerViewModel. GetViewModelStore (). FragmentManagerViewModel inherits from ViewModel, which defines the interaction between the Fragment and ViewModel. Looking further, you can see that it defines a function: void clearNonConfigState(@nonnull Fragment f):

void clearNonConfigState(@NonNull Fragment f) { if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "Clearing non-config state for " + f); } // Clear and remove the Fragment's child non config state FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho); if (childNonConfig ! = null) { childNonConfig.onCleared(); mChildNonConfigs.remove(f.mWho); } // Clear and remove the Fragment's ViewModelStore ViewModelStore viewModelStore = mViewModelStores.get(f.mWho); if (viewModelStore ! = null) { viewModelStore.clear(); mViewModelStores.remove(f.mWho); }}Copy the code

This includes clearing viewModels and untying fragments. Let’s take a look at where it is called and see if we can find the code associated with the lifecycle:

  • FragmentStateManager.destory
  • FragmentManager.moveToState
  • FragmentActivity–FragmentController–FragmentManager

It also covers the binding of Activity and Fragment lifecycle. you can look at it yourself sometime. Look at the FragmentStateManager. Destory:

void destroy(@NonNull FragmentHostCallback<? > host, @NonNull FragmentManagerViewModel nonConfig) { if (FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(TAG, "movefrom CREATED: " + mFragment); } boolean beingRemoved = mFragment.mRemoving && ! mFragment.isInBackStack(); boolean shouldDestroy = beingRemoved || nonConfig.shouldDestroy(mFragment); if (shouldDestroy) { boolean shouldClear; if (host instanceof ViewModelStoreOwner) { shouldClear = nonConfig.isCleared(); } else if (host.getContext() instanceof Activity) { Activity activity = (Activity) host.getContext(); shouldClear = ! activity.isChangingConfigurations(); } else { shouldClear = true; } if (beingRemoved || shouldClear) { nonConfig.clearNonConfigState(mFragment); } mFragment.performDestroy(); mDispatcher.dispatchOnFragmentDestroyed(mFragment, false); } else { mFragment.mState = Fragment.ATTACHED; }}Copy the code

If the context is an activity, it determines whether the destory is caused by a configuration change, and if not, it does viewModel-related cleanup.

Expand functionality

In the above chapters, we left two pieces of things didn’t speak: SavedStateViewModelFactory and its the create function. In this section, we’ll focus on the purpose of this class.

Here is a scenario: After an Activity is destroyed due to system resource constraints, can the data that was loaded and the user state be restored when the Activity is rebuilt?

In fact, the use of ViewModel extension can also be done, let’s start with a demo:

// ExpandViewModel.java
class ExpandViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    companion object {
        private const val KEY_EXPAND = "key_expand"
    }

    val expandLiveData = savedStateHandle.getLiveData<String>(KEY_EXPAND)
}

// MainActivity.java
class MainActivity : AppCompatActivity() {

    private lateinit var expandViewModel: ExpandViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initData()
    }

    private fun initData() {
        expandViewModel = ViewModelProvider(this).get(ExpandViewModel::class.java)
        findViewById<Button>(R.id.bt_update).setOnClickListener {
            expandViewModel.expandLiveData.value = "expand live data"
        }
        Log.i("MainActivity", "savedStateViewModel: $expandViewModel")
        Log.i("MainActivity", "savedStateViewModel.name: ${expandViewModel.expandLiveData.value}")
    }
}
Copy the code
  • Update by clickingexpandLiveDataThe value of the
  • In developer mode, on: Does not keep the Activity, used to simulate the Activity being destroyed due to lack of system memory

How is this different from the example we started with? Main differences:

  • The custom ofExpandViewModelThe constructor has been added with oneSavedStateHandleThe arguments
  • Data storage and acquisition throughSavedStateHandleoperation

So how does it do that? And then in-depth analysis SavedStateViewModelFactory. Create:

@NonNull @Override public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) { boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass); Constructor<T> constructor; if (isAndroidViewModel && mApplication ! = null) { constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE); } else { constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE); } // doesn't need SavedStateHandle if (constructor == null) { return mFactory.create(modelClass); } SavedStateHandleController controller = SavedStateHandleController.create( mSavedStateRegistry, mLifecycle, key, mDefaultArgs); try { T viewmodel; if (isAndroidViewModel && mApplication ! = null) { viewmodel = constructor.newInstance(mApplication, controller.getHandle()); } else { viewmodel = constructor.newInstance(controller.getHandle()); } viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller); return viewmodel; } catch (IllegalAccessException e) { throw new RuntimeException("Failed to access " + modelClass, e); } catch (InstantiationException e) { throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e); } catch (InvocationTargetException e) { throw new RuntimeException("An exception happened in constructor of " + modelClass, e.getCause()); }}Copy the code

There are three main judgments:

  • Is modelClass a subclass of AndroidViewModel? If so and mApplication is not empty, execute:
private static final Class<? >[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class, SavedStateHandle.class}; constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);Copy the code
  • Otherwise, run the following command:
private static final Class<? >[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class}; constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);Copy the code
  • If none of the above is met, execute:
// doesn't need SavedStateHandle
if (constructor == null) {
    return mFactory.create(modelClass);
}
Copy the code

In summary, there are two main categories of processing: whether the constructor contains savedStateHandle.class as an input parameter. In the demo, we added a SavedStateHandle entry to ExpandViewModel, so the process will execute down:

SavedStateHandleController controller = SavedStateHandleController.create(
        mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        
// 
Copy the code

Here will have another class: SavedStateHandleController, then we look at it the create function:

static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
        String key, Bundle defaultArgs) {
    Bundle restoredState = registry.consumeRestoredStateForKey(key);
    SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
    SavedStateHandleController controller = new SavedStateHandleController(key, handle);
    controller.attachToLifecycle(registry, lifecycle);
    tryToAddRecreator(registry, lifecycle);
    return controller;
}
Copy the code

We mainly did the following things:

  • throughregistry.consumeRestoredStateForKey(key)Retrieve previously saved data
  • throughSavedStateHandle.createHandle(restoredState, defaultArgs)Save the previous data toThe SavedStateHandle mRegularIn the
  • throughcontroller.attachToLifecycle(registry, lifecycle);willSavedStateHandletheSavedStateProviderRegistered toregistryWhat’s going on here? The back analysis
  • createSavedStateHandleControllerAnd return

So how is the data stored? And then how did registry get it? Let’s look at registry:

// SavedStateViewModelFactory.java @SuppressLint("LambdaLast") public SavedStateViewModelFactory(@Nullable Application application, @NonNull SavedStateRegistryOwner owner, @Nullable Bundle defaultArgs) { mSavedStateRegistry = owner.getSavedStateRegistry(); mLifecycle = owner.getLifecycle(); mDefaultArgs = defaultArgs; mApplication = application; mFactory = application ! = null ? ViewModelProvider.AndroidViewModelFactory.getInstance(application) : ViewModelProvider.NewInstanceFactory.getInstance(); } // ComponentActivity.java final SavedStateRegistryController mSavedStateRegistryController = SavedStateRegistryController.create(this); @NonNull @Override public final SavedStateRegistry getSavedStateRegistry() { return mSavedStateRegistryController.getSavedStateRegistry(); } // SavedStateRegistryController.java private SavedStateRegistryController(SavedStateRegistryOwner owner) { mOwner = owner; mRegistry = new SavedStateRegistry(); } /** * Returns controlled {@link SavedStateRegistry} */ @NonNull public SavedStateRegistry getSavedStateRegistry() { return mRegistry; }Copy the code

Through layers of lookup, finally Registry points directly to: SavedStateRegistry:

An interface for plugging components that consumes and contributes to the saved state. This objects lifetime is bound to  the lifecycle of owning component: when activity or fragment is recreated, new instance of the object is created as well. @SuppressLint("RestrictedApi") public final class SavedStateRegistry { /** * An interface for an owner of this @{code {@link SavedStateRegistry} to restore saved state. * */ @SuppressWarnings("WeakerAccess") @MainThread void performRestore(@NonNull Lifecycle lifecycle, @Nullable Bundle savedState) { if (mRestored) { throw new IllegalStateException("SavedStateRegistry was already restored."); } if (savedState ! = null) { mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY); } lifecycle.addObserver(new GenericLifecycleObserver() { @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event == Lifecycle.Event.ON_START) { mAllowingSavingState = true; } else if (event == Lifecycle.Event.ON_STOP) { mAllowingSavingState = false; }}}); mRestored = true; } /** * An interface for an owner of this @{code {@link SavedStateRegistry} * to perform state saving, it will call all registered providers and * merge with unconsumed state. * * @param outBundle Bundle in which to place a  saved state */ @MainThread void performSave(@NonNull Bundle outBundle) { Bundle components = new Bundle(); if (mRestoredState ! = null) { components.putAll(mRestoredState); } for (Iterator<Map.Entry<String, SavedStateProvider>> it = mComponents.iteratorWithAdditions(); it.hasNext(); ) { Map.Entry<String, SavedStateProvider> entry1 = it.next(); components.putBundle(entry1.getKey(), entry1.getValue().saveState()); } outBundle.putBundle(SAVED_COMPONENTS_KEY, components); }}Copy the code

Official introduction, you can see it is the actual place to save and restore data. Let’s look at its performSave: it’s going to iterate over the mComponents, and then save the components in the outBundle. Let’s look at the definition and use of mComponents:

private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>(); @MainThread public void registerSavedStateProvider(@NonNull String key, @NonNull SavedStateProvider provider) { SavedStateProvider previous = mComponents.putIfAbsent(key, provider); if (previous ! = null) { throw new IllegalArgumentException("SavedStateProvider with the given key is" + " already registered"); } } /** * Unregisters a component previously registered by the given {@code key} * * @param key a key with which a component was previously registered. */ @MainThread public void unregisterSavedStateProvider(@NonNull String key) { mComponents.remove(key); }Copy the code

It is a map for storing the SavedStateProvider. How does SavedStateHandle relate to it? In fact in SavedStateHandleController. In the create method is mentioned:

void attachToLifecycle(SavedStateRegistry registry, Lifecycle lifecycle) {
    if (mIsAttached) {
        throw new IllegalStateException("Already attached to lifecycleOwner");
    }
    mIsAttached = true;
    lifecycle.addObserver(this);
    registry.registerSavedStateProvider(mKey, mHandle.savedStateProvider());
}
Copy the code

Through the registry. RegisterSavedStateProvider (mKey, mHandle savedStateProvider ()); Register the savedStateProvider of SavedStateHandle with Registry.

Source code as you can see, the above SavedStateRegistryController is actually holds the SavedStateRegistry instance, all operations are invoked the indirect SavedStateRegistry. Let’s go back to ComponentActivity:

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { // Restore the Saved State first so that it is available to // OnContextAvailableListener instances mSavedStateRegistryController.performRestore(savedInstanceState); mContextAwareHelper.dispatchOnContextAvailable(this); super.onCreate(savedInstanceState); ReportFragment.injectIfNeededIn(this); if (mContentLayoutId ! = 0) { setContentView(mContentLayoutId); } } @CallSuper @Override protected void onSaveInstanceState(@NonNull Bundle outState) { Lifecycle lifecycle = getLifecycle(); if (lifecycle instanceof LifecycleRegistry) { ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED); } super.onSaveInstanceState(outState); mSavedStateRegistryController.performSave(outState); }Copy the code
  • inonCreateCall themSavedStateRegistryController.performRestore(savedInstanceState);
  • inonSaveInstanceStateCall themSavedStateRegistryController.performSave(outState);

conclusion

Through layer upon layer analysis, we can make the following conclusions:

  • Actually,SavedStateHandleAnd finally throughComponentActivitytheonSaveInstanceState(Bundle)Realize the data preservation and recovery
  • Only one is provided for the user layerSavedStateHandleFor the operation of data saving and recovery, the actual implementation is all hiddenSavedStateRegistryIn, it is the ability that really provides this piece.
  • If usedSavedStateHandleIf you want to retain data, you need to use what it provides:getLiveData(@NonNull String key)andsetValue(T value). Because of its membersmRegularIt is used to store the data to be recovered during the data reconstruction process.