In the JectPack component, ViewModel is mainly used to encapsulate the data related to the interface. Similarly, ViewModel has the ability of life cycle awareness. It always exists in memory before the destruction of Activity or Fragment onDetach; Especially after system configuration changes such as screen rotation, the interface data saved by ViewModel still exists
1. Creation of ViewModel
It is also possible to create a ViewModel by directly creating a new object, but the official method is to obtain it through ViewModelProvider + get. In Kotlin, this method is encapsulated into the syntax sugar of viewModels and activityViewModels to create viewModels through these two methods
@MainThread public inline fun <reified VM : ViewModel> Fragment.viewModels( noinline ownerProducer: () -> ViewModelStoreOwner = { this }, noinline factoryProducer: (() -> Factory)? = null ): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
Here, take viewModels, this is an inline advanced function. When it is called in the onCreateView method of Fragment, it directly completes the creation of ViewModel.
val viewModel = viewModels<MyViewModel>()
viewModels has two parameters, ownerProducer. The return is a ViewModelStoreOwner. The default is this, which is the current Fragment calling the function; factoryProducer, the factory that creates the ViewModel, can be empty and cannot be transferred
If you want to use your own factory to create ViewModel, you need to implement viewmodelprovider Factory interface
class ViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return modelClass.getConstructor(MyViewModel::class.java).newInstance() } }
Use as follows
val viewModel = viewModels<MyViewModel>{ ViewModelFactory() }
Another way is to create viewModels with activityViewModels, which requires a context. Others are the same as viewModels. The differences between the two will be explained later.
2 ViewModel source code analysis
Both viewModels and activityViewModels will go to the createViewModelLazy method,
@MainThread public fun <VM : ViewModel> Fragment.createViewModelLazy( viewModelClass: KClass<VM>, storeProducer: () -> ViewModelStore, factoryProducer: (() -> Factory)? = null ): Lazy<VM> { val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory } return ViewModelLazy(viewModelClass, storeProducer, factoryPromise) }
For viewModels, if the incoming factoryProducer is empty, a default ViewModelProviderFactory will be created
Fragment # getDefaultViewModelProviderFactory
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { if (mFragmentManager == null) { throw new IllegalStateException("Can't access ViewModels from detached fragment"); } if (mDefaultFactory == null) { Application application = null; Context appContext = requireContext().getApplicationContext(); while (appContext instanceof ContextWrapper) { if (appContext instanceof Application) { application = (Application) appContext; break; } appContext = ((ContextWrapper) appContext).getBaseContext(); } if (application == null && FragmentManager.isLoggingEnabled(Log.DEBUG)) { Log.d(FragmentManager.TAG, "Could not find Application instance from " + "Context " + requireContext().getApplicationContext() + ", you will " + "not be able to use AndroidViewModel with the default " + "ViewModelProvider.Factory"); } mDefaultFactory = new SavedStateViewModelFactory( application, this, getArguments()); } return mDefaultFactory; }
Finally, a SavedStateViewModelFactory is returned, which is the default factory. Here, it will judge whether the context application is empty. If it is empty, mFactory = NewInstanceFactory; Otherwise, it is AndroidViewModelFactory. When activityViewModels is called, a context will be passed in, and the source code can be viewed by yourself
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(); }
Finally, the ViewModel is obtained in the ViewModelLazy method
ViewModelProvider # ViewModelLazy
public class ViewModelLazy<VM : ViewModel> ( private val viewModelClass: KClass<VM>, private val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory ) : Lazy<VM> { private var cached: VM? = null override val value: VM get() { val viewModel = cached return if (viewModel == null) { val factory = factoryProducer() val store = storeProducer() ViewModelProvider(store, factory).get(viewModelClass.java).also { cached = it } } else { viewModel } } override fun isInitialized(): Boolean = cached != null }
First, get the ViewModel from the cache. If there is no ViewModel in the cache, get it through ViewModelProvider + get. Here we mainly look at the get method
ViewModelProvider # get
@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); }
Here is the full class name and default of the class_ Key is spliced into a key and retrieved from mViewModelStore. mViewModelStore is the default this in viewModels. Each Activity and Fragment has an mViewModelStore
Both Activity and Fragment implement the ViewModelStoreOwner interface. There is a method in this interface
public interface ViewModelStoreOwner { @NonNull ViewModelStore getViewModelStore(); }
When createViewModelLazy is called, ViewModelStore is actually passed in. ViewModelStore is actually a HashMap
ComponentActiivty # getViewModelStore
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; } @SuppressWarnings("WeakerAccess") /* synthetic access */ 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(); } } }
Through the spliced key, go to mViewModelStore to get the ViewModel. If you haven't created a ViewModel for the first time, you can't get it. Then go to the position of 1
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); //Come in for the second time and take this position 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. } } //1 the first one to come in and take this position if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); } mViewModelStore.put(key, viewModel); return (T) viewModel; }
Here, we will judge the type of the incoming factory. Generally, we will use else and the create method of the project we created ourselves
ViewModelProvider # Factory – create
public interface Factory { @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass); }
In this way, a ViewModel is created and saved to mViewModelStore;
Therefore, when creating a ViewModel, viewModels first takes it from the cache. If it is not in the cache, it gets it from the mViewModelStore. If it is not in the mViewModelStore, it is recreated. It is equivalent to a three-level cache, which is a singleton mode.
3 ViewModel storage data
When the screen rotates or other configurations change, the Activity will be destroyed and rebuilt. The page data is saved. Usually, onSaveInstanceState is called when the page is destroyed and onRestoreInstanceState is called when the page is rebuilt. This can only save lightweight data (because of the Bundle). Therefore, the emergence of ViewModel solves the problem that the traditional method can not store a large amount of data
In the activity, there are two non lifecycle methods, onRetainNonConfigurationInstance and getLastNonConfigurationInstance
ComponentActivity # onRetainNonConfigurationInstance
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 is empty, //Or the user-defined status is empty, and there is no user-defined saving status, that is, it does not need to be saved if (viewModelStore == null && custom == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; }
Onretainumconfigurationinstance will be called when the screen is rotating. Onretainumcustomnonconfigurationinstance will be called when the page is ready to be destroyed. This method can be rewritten to customize the storage state of page data. At this time, get mViewModelStore, and create NonConfigurationInstances before clearing it from memory, Save viewModelStore and data save status to NonConfigurationInstances
Activity # retainNonConfigurationInstances
NonConfigurationInstances retainNonConfigurationInstances() { // Call onRetainNonConfigurationInstance in ComponentActivity // What is saved in the activity is the viewModel and the data store state Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); mFragments.doLoaderStart(); mFragments.doLoaderStop(true); ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.activity = activity; nci.children = children; nci.fragments = fragments; nci.loaders = loaders; if (mVoiceInteractor != null) { mVoiceInteractor.retainInstance(); nci.voiceInteractor = mVoiceInteractor; } return nci; }
Finally, before the Activity is destroyed, all the Activity related information is saved in NonConfigurationInstances
After the Activity is destroyed and restarted, lastNonConfigurationInstances is passed in the attach method, which is the status information saved before the page is destroyed
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, //Saved page data information NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken, IBinder shareableActivityToken)
Here's how to transfer it. Add a TODO, and then focus on the source code in AMS. In this way, after the Activity is restarted, go to getViewModelStore again and call getLastNonConfigurationInstance
Activity # getLastNonConfigurationInstance
public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null; }
Here, mLastNonConfigurationInstances is no longer empty (passed in the attach), and mLastNonConfigurationInstances is returned activity, which saves the viewModelStore and the saved data state of the page, so as to save the page data
How the ViewModel is destroyed after the page is destroyed is the credit of LifeCycle
Construction method of 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(); } } } });
When supervisor hears Activity on_ After destroy, all viewmodels in the ViewModelStore will be cleared. Of course, after saving the data