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 verticalActvitiyThread# performDestroyActivity method performs Activiy# retainNonConfigurationInstancesSo 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 fineTo detectLifecycleThe destruction signal sent executes the cleanup methodViewModelStore#clearMethod traverses the map to empty the ViewModel and empty the HashMap

You can override it when you inherit the ViewModelonClearedMethod 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#attachControllerThere 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