Architecture Components source code analysis ViewModel

If you still don't know what a ViewModel is, you can look at it.[Translation] View Components of Architecture Components This series of articles, translated from the official article of Android Developer.

The ViewModel class is designed to store and manage UI-related data, and implements two main functions:

  1. Data can be retained during configuration changes such as screen rotation.
  2. Share data between Fragments.

Next, we will look at how to implement these two functions by analyzing the source code.

Let's first find the class of ViewModel.

ViewModel

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

The discovery is just an abstract class, and there is only one empty implementation method, indicating that the code that implements the special function must be elsewhere.
After reading the official introduction, you should know that ViewModel is created by ViewModelProvider:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
                         // update UI
        });
    }
}

Then we start analyzing from here, by callingViewModelProviders.of(this).get(MyViewModel.class)How did you get the ViewModel? Let's seeViewModelProvidersofof()Method (of method overloading and Fragment, here we only analyze Activity, Fragment and activity are exactly the same):

    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

    @SuppressLint("StaticFieldLeak")
    private static DefaultFactory sDefaultFactory;

    private static void initializeFactoryIfNeeded(Application application) {
        if (sDefaultFactory == null) {
            sDefaultFactory = new DefaultFactory(application);
        }
    }

    private static Application checkApplication(Activity activity) {
        Application application = activity.getApplication();
        if (application == null) {
            throw new IllegalStateException("Your activity/fragment is not yet attached to "
                    + "Application. You can't request ViewModel before onCreate call.");
        }
        return application;
    }

ViewModelProviders.of()The method returns a ViewModelProvider object that takes two arguments: ViewModelStore, Factory. By naming, we can guess that ViewModelStore is a repository of ViewModel for caching ViewModel, and Factory is a factory class for creating ViewModel instances. After getting the ViewModelProvider object, it calls it again.getThe method gets the ViewModel object and looks at this method:

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);
    }

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

The parameters ViewModelStore and Factory that will be constructed in the ViewModelProvider class are used as member variables.getMethod first frommViewModelStore.getGet in, if not obtained, passFactoryCreateViewModelInstance and putViewModelStoreIn this way, this usage method verifies our guess above and will analyze it carefully.ViewModelStoreas well asFactory

Since there is oneViewModelCacheViewModelStore, the first function: the data can be retained when the configuration changes (such as: screen rotation), it is well understood. Just let the cache rebuild in the Activity configuration change is survived, then the rebuild is obtainedViewModelIt is the one that was cached before.

The next question is: ViewModelStoreWhere can I save the survival of the Activity configuration changes?

ViewModelStore

Literally means the warehouse of ViewModel

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

This is very simple and well understood. It is just a HashMap for storing ViewModels, providing methods for putting, getting, and emptying.

We are backViewModelProviders.of()Method, here is throughViewModelStores.of(activity)To get the ViewModelStore object, we continue to enter this method:

import static android.arch.lifecycle.HolderFragment.holderFragmentFor;

public class ViewModelStores {

    private ViewModelStores() {
    }

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }
}

Noticed that it was called by a statically introduced methodHolderFragmentofholderFragmentFor ()Method, then findHolderFragment

HolderFragment

public class HolderFragment extends Fragment {

    ...//Removed the code related to framgent, only retain activity related

    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    public static final String HOLDER_TAG =
            "android.arch.lifecycle.state.StateProviderHolderFragment";

    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }


    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

    static class HolderFragmentManager {
        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();

        private ActivityLifecycleCallbacks mActivityCallbacks =
                new EmptyActivityLifecycleCallbacks() {
                    @Override
                    public void onActivityDestroyed(Activity activity) {
                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
                        if (fragment != null) {
                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
                        }
                    }
                };

        private boolean mActivityCallbacksIsAdded = false;

        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);
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }
}

This class is the core class of ViewModel. All the functions are implemented by this class. Please pay attention to it~

HolderFragment.holderFragment()The method returns directlysHolderFragmentManager.holderFragmentFor(activity)the result of. andHolderFragmentManagerofholderFragmentForThe method actually creates an instance of HolderFragment and adds it to the parameter activity. In order to avoid repeated additions, the first call isfindHolderFragment(fm) See if you can find the HolderFragment, if you don't have it, look it up from the cached Map, or if you have nothing to create a new instance, put it into the cached Map, and return the object, then callgetViewModelStore()ObtainviewModelStoreExample.

We foundViewModelStoreThe storage location is in the HolderFragment, how does it guarantee that the Activity configuration change rebuild is alive? In fact, the key code is a method of Fragment:

/**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */
setRetainInstance(true);

This method ensures that when the activity is rebuilt due to configuration changes, the instance of the fragment will not be destroyed, and the reconstructed Activity still uses the instance.

There are many more details on the process of creating a HolderFragment.

have to be aware of isHolderFragmentManagerIs stated inHolderFragmentIn the static member, so will followHolderFragmentThe first time the instance is created, only one instance exists and is always in memory, the cached map isHolderFragmentManagerMember variables will also always be in memory, andHolderFragmentCan create multiple instances, so for no longer neededHolderFragmentThe instance needs to be removed from the map in time.

if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }

This code is to register a global Activity lifecycle callback through Application's registerActivityLifecycleCallbacks. Any Activity triggers the lifecycle to callback the corresponding method in mActivityCallbacks.HolderFragmentThe source code is through the callback, in the bindingHolderFragmentThe Activity triggers the onDestroy method to remove the cache from the map.

At first I thoughtHolderFragmentManagerWill cacheHolderFragmentThe cache will not be removed until the attached activity is destroyed, but later noticedHolderFragmentofonCreateCalled in the methodsHolderFragmentManager.holderFragmentCreated(this);The cache was removed directly. So this cache is just fromHolderFragmentAdd method call toonCreateThe method is executed. Or add a Fragment but have not added to the Activity to execute the onCreate method, the attached Activity is destroyed, and the onDestroy method of the mActivityCallbacks is also called back to remove the HolderFragment cache. I thought for a long time and did not think of the use of this cache, as if this cache is meaningless.

2017.12.27 Supplement:
JessYanPoint, I realizedHolderFragmentThe cache is very meaningful. Without this cache, Fragment has not added execute onCreate when it is called twice to get the ViewModel. This will create two HolderFragment instances. And if this happens between two Fragments, the different ViewModel instances are obtained, and the communication between Fragments cannot be realized. I verified this with the Android profiler of as3.0 and did create two instances!

Factory

The factory class that creates the ViewModel is an interface that we can implement to define our own ViewModel factory.

/**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

Aboveof()in usesDefaultFactoryDefault factory:

public static class DefaultFactory extends ViewModelProvider.NewInstanceFactory {

        private Application mApplication;

        /**
         * Creates a {@code DefaultFactory}
         *
         * @param application an application to pass in {@link AndroidViewModel}
         */
        public DefaultFactory(@NonNull Application application) {
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    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);
                }
            }
            return super.create(modelClass);
        }
    }

public static class NewInstanceFactory implements Factory {

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                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);
            }
        }
    }

DefaultFactory can be createdAndroidViewModelObject, call itAndroidViewModel(@NonNull Application application)Constructs an instance. If it is not AndroidViewModel.class, it calls the create method of the parent class NewInstanceFactory to call the constructor of the ViewModel with no parameters.
If your ViewModel instance needs to be created with other parameters, you need to implement Factory replication to create it yourself.

to sum up

ViewModelProviders.of()provideViewModelProviderViewModelProviderbyViewModelStorewithFactoryManage and create ViewModels,ViewModelStoreThe reference is stored in the no interface added to the target Activity/FragmentHolderFragmentIn, and throughsetRetainInstance(true);To ensure that the reconstruction in the Activity configuration changes is surviving.

About the second function: sharing data between Fragment is also well understood, using different Fragment types in the same Activity.ViewModelProviders.of()When the parameter needs to be passed to the Activity object, the first time the ViewModel gets a new object, and the other Fragment gets the same ViewModel, it willViewModelStoreIn the cache, when the two Fragments are held by the same ViewModel object, the communication between the Fragments can be realized. But the future of this communication must be in the same Activity.

Intelligent Recommendation

ViewModel of Android Architecture Components

ViewModel concept and usage ViewModel is used to store and manage UI-related data. It can abstract the data logic related to an Activity or Fragment component and adapt to the life cycle of the compon...

Probe into the ViewModel of Architecture Components

Original address:Official Document-ViewModel Demo address:Kotlin-Dagger-2-Retrofit-Android-Architecture-Components The ViewModel class is used to store and manage UI-related data so that the data can ...

JetPack Architecture Components: ViewModel

Introduction In Activity / Fragment, the UI interaction, the business logic related to the data acquisition, etc. is usually written in the page, but when the page is complex, the amount of code will ...

Android architecture-ViewModel source code learning summary

This article is a summary of the host's learning of the ViewModel source code. I feel that the ViewModel source code is the easiest to understand among the three Android architectures. Structure of th...

DataBinding source code analysis of Android architecture components

DataBinding is a support library released by Google that enables bidirectional binding of UI components and data sources. DataBinding can be used to easily implement the MVVM mode. When the data chang...

More Recommendation

LiveData source code analysis of Android architecture components

LiveData is an architectural component released by Google. It is a data holding class and data can be observed. Different from ordinary observers, the biggest feature of LiveData is its lifecycle awar...

Analysis of some source code of Android Architecture Components

Android Architecture Components is a new set of architecture components released by Google to make the App architecture more robust Personal understanding: please be kind if your understanding is wron...

ViewModel basic usage and source code analysis

Foreword To undertake the learning sequence of the previous article, this article is mainlyViewModel Learning.ViewModel Is the class used to hold UI data and will continue to exist after a configurati...

Android Jetpack-ViewModel source code analysis

reference: ViewModelProviders.java Note 1: To store the ViewModel, first obtain the ViewModel object through the get() method of ViewModelStore. If for the first time, the ViewModel object must be emp...

Android Jetpack series-ViewModel source code analysis

This article has authorized the WeChat public account Guo Lin (guolin_blog) to reprint. This article is mainly aboutViewModelPerform source code analysis, it is recommended to faceSample codeRead the ...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top