Jetpack has been out for a long time, but recently I saw someone ripping the ViewModel again. I went to revisit it with some questions
Before you ask a question, you can briefly review what you’ve writtenJetpack MVVM Trilogy part 1 ViewModel
I added comments to the snippet to make it easier to see
1. How to create ViewModel?
Start with the code you created
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
Copy the code
Let’s take a look at the ViewModelProvider method in this code
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { // One notable detail about the constructor argument is that the tri operator, which starts with this being an entity class, can be seen as ComponentActivity itself, so owner instanceof HasDefaultViewModelProviderFactory this is formed of this (owner. GetViewModelStore (), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; } @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); } @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: If (mFactory instanceof KeyedFactory) {log a warning.}} / / here is actually called SavedStateViewModelFactory# create methods / / space relation was slightly off here And finally here is through reflection instantiation viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass); } else { viewModel = (mFactory).create(modelClass); } // This mViewModelStore is actually Activtiy mViewModelStore mViewModelStore. Put (key, viewModel); return (T) viewModel; }Copy the code
The ComponentActivity is realized ViewModelStoreOwner HasDefaultViewModelProviderFactory, both of the interface
@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."); } the if (mViewModelStore = = null) {/ / initial is set to be NonConfigurationInstances take empty / / here, See the ams knowledge here is very good classmate estimate already know the answer to the second NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) {/ / recovery from NonConfigurationInstances ViewModelStore mViewModelStore = nc. ViewModelStore; If (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); } } return mViewModelStore; }Copy the code
The following is HasDefaultViewModelProviderFactory implementation
@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; } / / SavedStateViewModelFactory here is inherited ViewModelProvider KeyedFactoryCopy the code
The constructor call to ViewModelProvider passes in an interface implementation class, this, so the constructor calls to a parameter are ComponentActivity#mViewModelStore and mDefaultFactory, So this gives you a sense of how to create the ViewModel
2. Did he restore it when the page was recreated?
So this is kind of the heart of ViewModel, and if you want to know the answer you have to look at this ViewModelStore thing, which has the whole ComponentActivity#getViewModelStore method, Just to make it easier to post the key code for geViewModelStore here
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { // Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore; }Copy the code
Stick a NonConfigurationInstances figure, simple is a static classKey point nc. ViewModelStore getLastNonConfigurationInstance under inquiry is what
@Nullable public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances ! = null ? mLastNonConfigurationInstances.activity : null; }Copy the code
First look for mLastNonConfigurationInstances assignment in looking for assignment of the activity
Here mLastNonConfigurationInstances method from the attach assignment, the attach is ActivityThread invoke this method calls (this involves ams start interested students can go to search related articles)
Here find the assignment mLastNonConfigurationInstances find the assignment of the activity
NonConfigurationInstances retainNonConfigurationInstances () {/ / the onRetainNonConfigurationInstance is notable Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); // We're already stopped but we've been asked to retain. // Our fragments are taken care of but we need to mark the loaders for retention. // In order to do this correctly we need to restart the loaders first before // handing them off to the next activity. 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; }Copy the code
Above onRetainNonConfigurationInstance method in the Activity is the return null is certainly have a place to rewrite the; So we found a rewrite of this method at ComponentActivity
/ / post NonConfigurationInstances under this is ComponentActivity internal static class static final class NonConfigurationInstances {Object custom; ViewModelStore viewModelStore; } /** * Retain all appropriate non-config state. You can NOT * override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to * retain your own non config state. */ @Override @Nullable public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; If (viewModelStore == null) {// No one calls getViewModelStore(), / / so see if we have a NonConfigurationInstanc have an existing ViewModelStore 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 is just saved so who call the Activity# retainNonConfigurationInstances method?
- This is where AMS will go when the Activity is in horizontal or vertical
ActvitiyThread# performDestroyActivity method performs Activiy# retainNonConfigurationInstances
So reliving the life cycle doesn’t lose the ViewModel data here; When you restore the page, you can retrieve the original data simply by using the getViewModelStore method
3. How was it destroyed?
Look directlyThe ComponentActivity constructor is fine
To detectLifecycle
The destruction signal sent executes the cleanup method 在ViewModelStore#clear
Method traverses the map to empty the ViewModel and empty the HashMap
You can override it when you inherit the ViewModelonCleared
Method to do extra things like disconnect the contextThis empty operation actually helps us separate the ViewModel from the Acitviy, so the ViewModel will be recycled normally when the page is destroyed without causing any memory leaks; Of course this is not absolute; After all, the root cause of a memory leak is a memory leak with a long life and a short life, which makes it impossible to recycle (GC analyzes whether the object is recyclable based on root reachable
); So you should pay more attention when writing code; After all, too many Memory leaks can cause Out Of Memory leaks
4. How to create a ViewModel in Fragment?
- I wanted to talk about this with point 1, but I’ll write a separate one for clarity
MyFragmentViewModel viewModel = new ViewModelProvider(this).get(MyFragmentViewModel.class);
Copy the code
The use of or as well as the Activity of fragments, the difference is that ViewModelStoreOwner fragments, HasDefaultViewModelProviderFactory realization and ComponentActivity different; Just look at the implementation of ViewModelStoreOwner
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
Copy the code
This mNonConfig is a subclass that inherits ViewMolde FragmentManagerViewModel How is it created
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
Copy the code
You can seeFragmentManagerImpl#attachController
There are 3 different methods that are created in this place to checkFirst, activities actually have their own FragmentController to manage the Fragment. Find FragmentActivity;
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
Copy the code
Here an empty parent is passed in conjunction with the above analysis;Creating the viewModelStore is the middle one and you go straight to getInstance;
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
Copy the code
FragmentManagerViewModel#getInstance is removed from the ViewModelStore get method and all that is left is to add this FragmentManagerViewModel to the Activity’s Viewmodel; Then get instantiates MyFragmentViewModel and adds that ViewModel to FragmentManagerViewModel.
@NonNull
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
return viewModelProvider.get(FragmentManagerViewModel.class);
}
Copy the code
Because of this, Activtiy and Fragment can get each other’s viewModels