New to the nuggets, unfamiliar with life, like friends, a thumbs up to encourage novice bai ~

Reference article:


https://developer.android.google.cn/topic/libraries/architecture/viewmodel


https://mp.weixin.qq.com/s/thoXHuXHC3sV90IFttHfXw


https://blog.csdn.net/gaugamela/article/details/56280384

The ViewModel class is primarily used to store and manage UI-related data, and it keeps the data from being destroyed if the UI is rebuilt due to configuration changes such as screen rotation.

ViewModel lifecycle

The time that a ViewModel object lives in the system without being reclaimed is determined by the Lifecycle that is created by the ViewModel and passed to the ViewModelProvider. The ViewModel will remain in memory until the end of its Lifecycle, which limits its lifetime. For an Activity, this is when the Activity destroys; For fragments, this is done in Fragment detach.

The following diagram illustrates the lifecycle of the Activity as it goes through the screen rotation until it finally destroys the destroyed window. The diagram also shows the ViewModel life cycle next to the associated Activity life cycle.

As you can see, the ViewModel life cycle runs through the Activity until the Activity ends normally, without prematurely ending the ViewModel life cycle due to system reasons such as screen rotation. The same is true of fragments for viewModels.

               

In this article, we won’t discuss the use of the ViewModel, but rather look directly at how it works.

ViewModel principle analysis

When we analyze the source code to take a clear purpose with the problem to analyze, from small to big, a small problem to solve, headless fly like into the source code, may get half the result with twice the effort.

Before we look at the ViewModel source code, let’s look at some important classes in the ViewModel component:

  • The ViewModel,ViewModelProvider,HolderFragment,ViewModelStore



The class diagram above gives a brief overview of the functions and relationships of these classes, but let’s dive right into the source code to analyze their implementation and how they relate to each other.

We know that the ViewModel instance is created through viewModelproviders.of (this).get(xxxViewModel.class). This code can be divided into two steps:

  • Step 1: PassViewModelProviders.of(this)generateViewModelProviderObject;

  • Step 2: PassviewModelProvider.get(XxxViewModel.class)Generate relativelyViewModelObject.

Our source code looks at some of the most important classes in the ViewModel architecture for these two steps.

ViewModelProvider

ViewModelProvider viewModelProviders.of (this)

public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        // In the class diagram above, we have already said that when generating the ViewModelProvider instance, if factory passes NULL,
        // The system will default to AndroidViewModelFactory as the ViewModel production class
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }

    // Instantiate to create a ViewModelProvider object
    return new ViewModelProvider(ViewModelStores.of(activity), factory);
}Copy the code

As you can see, viewModelproviders.of () will eventually return a new ViewModelProvider object. When you create a ViewModelProvider, you pass in two parameters, ViewModelStore and Factory. We won’t talk about how ViewModelStores. Of (Activity) instantiates the return ViewModelStore object here, but we’ll talk about that later.

Let’s start with the Factory class, whose definition is simple:

public interface Factory {
    // Only one create method needs to be overridden. This method returns the ViewModel object,
    // We can choose to use AndroidViewModelFactory by default, or we can customize the Factory
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}Copy the code

AndroidViewModelFactory is used by default when we pass the factory argument null:

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    / / the singleton
    @NonNull
    public static AndroidViewModelFactory getInstance(@NonNull Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    public AndroidViewModelFactory(@NonNull Application application) {
        mApplication = application;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {

        // When modelClass inherits from AndroidViewModel
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            //noinspection TryWithIdenticalCatches
            try {
                // Create a ViewMode return by calling the constructor of the modelClass class with the Application argument
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}AndroidViewModelFactory is called if the modelClass class does not inherit from AndroidViewModel
        // The create method of the parent class NewInstanceFactory returns a ViewModel object
        return super.create(modelClass); }}Copy the code

AndroidViewModelFactory’s parent class, NewInstanceFactory, is also very simple:

public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            Instantiate a ViewModel object by calling the parameterless constructor of the modelClass directly via reflection
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}}Copy the code

Based on the above analysis of Factory, we can draw the following conclusions on the premise that the Factory parameter passed in is null:

  • If our custom model inherits fromViewModel, requires a default constructor with no arguments;
  • If our custom model inherits fromAndroidViewModelThere must be aApplicationIs a constructor with a unique argument.

Of course, we can also customize Factory, here will not discuss, the majority of smart readers is the most powerful ~

ViewModelStore

We know that when constructing the ViewModelProvider, we get a ViewModelStore object through the viewModelStore.of () method. The ViewModelStores class is used to provide objects that return ViewModelStore. What does ViewModelStores do?

public class ViewModelStores {

    private ViewModelStores(a) {}@NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {

        // The developer can choose to have the Activity inherit ViewModelStoreOwner and implement the getViewModelStore method
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        
        // The holderFragmentFor(Activity) method returns an instance of HolderFragment that inherits ViewModelStoreOwner,
        // Call getViewModelStore() on the HolderFragment object to get the ViewModelStore instance.
        return holderFragmentFor(activity).getViewModelStore();
    }

    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        // In the same way, fragment can inherit ViewModelStoreOwner and implement getViewModelStore
        if (fragment instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) fragment).getViewModelStore();
        }
        / / same as above
        returnholderFragmentFor(fragment).getViewModelStore(); }}Copy the code

As you can see, the viewModelStore.of () method basically gets the ViewModelStore object from the ViewModelStoreOwner object. What exactly does the holderFragmentFor() method do? Let’s continue our analysis. For now, let’s look at the ViewModelStore:

public class ViewModelStore {
    
    // Use HashMap to store ViewModel objects
    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    / / new ViewModel
    final void put(String key, ViewModel viewModel) {
        // If the ViewModel for the key already exists, override it and call its onCleared method
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}// Get the ViewModel for the specified key
    final ViewModel get(String key) {
        return mMap.get(key);
    }
    
    // Call the onCleared method on all viewModels and clear the entire HashMap
    public final void clear(a) {
        for(ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); }}Copy the code

The implementation of ViewModelStore is very simple, the main function is to store and manage the ViewModel, through a HashMap to save all viewModels, respectively through the put method and get method to add and obtain viewModels, Call all viewModels’ onCleared methods through the clear method and clear the map.

Next, let’s look at one of the most core classes, HolderFragment.

HolderFragment

The holderFragmentFor() method returns an instance of a HolderFragment that inherits ViewModelStoreOwner. Now what is the HolderFragment class? Why is the Fragment instance not destroyed when the Activity is destroyed by the system due to screen rotation, etc?

public class HolderFragment extends Fragment implements ViewModelStoreOwner {
    private static final String LOG_TAG = "ViewModelStores";

    // What is this? See the comments below for analysis
    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String HOLDER_TAG = "android.arch.lifecycle.state.StateProviderHolderFragment";

    // This is the ViewModelStore where we store viewModels, defined in the HolderFragment
    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment(a) {
        // Highlight!! Why is it that when an activity is destroyed by the system due to screen rotation, etc,
        // Will the fragment instance not be destroyed? Because I set setRetainInstance(true)
        setRetainInstance(true);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // When the Fragment onCreate method is executed, the Fragment has been successfully added to the Activity.
        // sHolderFragmentManager is the HolderFragmentManager class, whose holderFragmentCreated() method
        / / the fragments from mNotCommittedActivityHolders or mNotCommittedFragmentHolders removed
        // (HolderFragmentManager description, see comment below)
        sHolderFragmentManager.holderFragmentCreated(this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onDestroy(a) {
        super.onDestroy();
        // When a Fragment with setRetainInstance(true) set onDestroy is called,
        // Prove that the Activity to which it is attached is dead, so call mviewModelStore.clear (),
        // As we said earlier, the clear method calls the onCleared method of all ViewModel objects
        // And to clear them, we can do something in the onCleared method of the ViewModel to avoid unnecessary actions
        // Memory leak and other problems
        mViewModelStore.clear();
    }

    // This method is used for external calls that return ViewModelStore
    @Override
    public ViewModelStore getViewModelStore(a) {
        return mViewModelStore;
    }

    // Static method, not called in ViewModelStores. Of method
    // Function: Add a HolderFragment to the Activity to store the ViewModelStore that holds the ViewModel object
    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

    // Static method, not called in ViewModelStores. Of method
    // Add a HolderFragment to the Fragment to store the ViewModelStore containing the ViewModel object
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static HolderFragment holderFragmentFor(Fragment fragment) {
        return sHolderFragmentManager.holderFragmentFor(fragment);
    }

    // Most of the operations above are based on the HolderFragmentManager. Let's examine this class
    @SuppressWarnings("WeakerAccess")
    static class HolderFragmentManager {

        // Stores the HolderFragment that has not been formally added to the Activity by the system
        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();

        / / statement defines a ActivityLifecycleCallbacks able to perceive the Activity lifecycle
        private ActivityLifecycleCallbacks mActivityCallbacks =
                new EmptyActivityLifecycleCallbacks() {
                    @Override
                    public void onActivityDestroyed(Activity activity) {
                        / / when the Activity destroy, remove mNotCommittedActivityHolders holds
                        // The corresponding HolderFragment. Previously we analyzed the onCreate method of HolderFragment
                        / / please mNotCommittedActivityHolders once, why in such even redundant? Actually,
                        // This is not superfluous, because the Activity may die before the Fragment is created
                        // HodlerFragment's onCreate cannot be called, so add another layer of cleanup to make sure it works
                        // Delete (have to sigh, Google official rigorous and control of the source code understanding ability)
                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
                        if(fragment ! =null) {
                            Log.e(LOG_TAG, "Failed to save a ViewModel for "+ activity); }}};private boolean mActivityCallbacksIsAdded = false;

        private FragmentLifecycleCallbacks mParentDestroyedCallback =
                new FragmentLifecycleCallbacks() {
                    @Override
                    public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
                        // Same as mActivityCallbacks analysis
                        super.onFragmentDestroyed(fm, parentFragment);
                        HolderFragment fragment = mNotCommittedFragmentHolders.remove(
                                parentFragment);
                        if(fragment ! =null) {
                            Log.e(LOG_TAG, "Failed to save a ViewModel for "+ parentFragment); }}};// This method is called when the HolderFragment's onCreate life cycle is called back
        / / mNotCommittedActivityHolders or mNotCommittedFragmentHolders
        // References the HolderFragment
        void holderFragmentCreated(Fragment holderFragment) {
            Fragment parentFragment = holderFragment.getParentFragment();
            if(parentFragment ! =null) {
                mNotCommittedFragmentHolders.remove(parentFragment);
                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                        mParentDestroyedCallback);
            } else{ mNotCommittedActivityHolders.remove(holderFragment.getActivity()); }}private static HolderFragment findHolderFragment(FragmentManager manager) {
            if (manager.isDestroyed()) {
                throw new IllegalStateException("Can't access ViewModels from onDestroy");
            }

            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
            if(fragmentByTag ! =null && !(fragmentByTag instanceof HolderFragment)) {
                throw new IllegalStateException("Unexpected "
                        + "fragment instance was returned by HOLDER_TAG");
            }
            return (HolderFragment) fragmentByTag;
        }

        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
            return holder;
        }

        HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if(holder ! =null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if(holder ! =null) {
                return holder;
            }

            if(! mActivityCallbacksIsAdded) { mActivityCallbacksIsAdded =true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);
            // The Fragment we add will not be executed immediately after the method is completed
            // The findHolderFragment above returns null. But that doesn't matter, because we still have
            / / to get to the corresponding instance from mNotCommittedActivityHolders), so we put him in first
            / / mNotCommittedActivityHolders. Not Committed Indicates that the commit of the fragment is Not complete
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

        HolderFragment holderFragmentFor(Fragment parentFragment) {
            FragmentManager fm = parentFragment.getChildFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if(holder ! =null) {
                return holder;
            }
            holder = mNotCommittedFragmentHolders.get(parentFragment);
            if(holder ! =null) {
                return holder;
            }

            parentFragment.getFragmentManager().registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
            holder = createHolderFragment(fm);
            / / same as above
            mNotCommittedFragmentHolders.put(parentFragment, holder);
            returnholder; }}}Copy the code

Through the above comments, we have done a detailed analysis of the HolderFragment core class, to summarize:

  • HolderFragmentBy setting thesetRetainInstance(true)So that it can survive without being affected by configuration changes such as screen rotation until it is attachedActivityNormal end.
  • becauseHolderFragmentThe life cycle of,ViewModelStoreThe object is saved inHolderFragment, and theViewModelAnd stored in theViewModelStoreMiddle, that’s why we say, rightViewModelClass to keep data from being destroyed in the event of a UI rebuild due to configuration changes such as screen rotation.

conclusion

So much for the ViewModel analysis, we solved two main problems:

  • How is the ViewModel created?

Get (xxxViewModel.class) or ViewModelProviders. Of (this, MFactory).get(xxxViewModel.class) returns ViewModel. The final ViewModel is actually created by Factory. When we don’t pass the Factory parameter, the system uses AndroidViewModelFactory as Factory by default and returns the ViewModel instance object through reflection.

  • How does the ViewModel and the data stored in it remain in memory despite screen rotation?

The GC garbage collection mechanism does not collect strongly referenced objects. During development, the data we need to store is referenced by the ViewModel, which is referenced by the ViewModelStore, which is referenced by the HolderFragment, thus forming a chain of references: HolderFragment->ViewModelStore->ViewModel-> The data we want to store (best practice is LiveData). The Fragment was created with setRetainInstance(True) set so that it could survive configuration changes such as screen rotation. Until the attached Activity ends properly.


Finally, I attach a sequence diagram for you to help you understand memory:



New to the nuggets, unfamiliar with life, like friends, a thumbs up to encourage novice bai ~