The ViewModel stores and manages data related to the interface in a life-cycle manner. When the system destroys or recreates an Activity/Fragment, the data stored in it disappears. For simple data, the Activity can use the onSaveInstanceState() method to recover data from the onCreate() bundle. But this method is only suitable for a small amount of data that can be serialized and deserialized, and not suitable for a potentially large amount of data, the emergence of the ViewModel, just to make up for this deficiency. On the other hand, activities/Fragments often need to make asynchronous calls, and make sure that onDestroy cleans up those calls to avoid potential memory leaks. This requires a lot of maintenance work and, in the case of recreating objects for configuration changes, wastes resources because calls that have already been made may need to be re-issued. The ViewModel decouples this work from Lifecycle and works with LiveData to avoid memory leaks.

One: the use of ViewModel

1.1 Adding a Dependency

Add the following dependencies to your app’s build.gradle:

 def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
Copy the code

1.2 implement the ViewModel

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData().also {
            loadUsers()
        }
    }
    fun getUsers(a): LiveData<List<User>> {
        return users
    }
    private fun loadUsers(a) {
        // Do an asynchronous operation to fetch users.}}Copy the code

Define a class that inherits the ViewModel. The ViewModel is responsible for preparing the data for the interface, so we usually use LiveData to host the data in the ViewModel. There is a list of users variable above

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        val model=ViewModelProvider(this).get(MyViewModel::class.java)
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI}}})Copy the code

Use the user list in the Activity.

  • Val Model =ViewModelProvider(this).get(MyViewModel::class.java) Then you can use liveData inside the ViewModel

Note: The ViewModel must not reference the View, Lifecycle, or any class that may store references to the Activity context to avoid memory leaks. \

If AndroidViewModel is inherited, then need to pass Factory to create ViewModel, the code is as follows

class RoomViewModel(val context:Application) :AndroidViewModel(context){
    var users = MutableLiveData<List<User>>()
}
Copy the code

The code for the corresponding Activity is as follows:

class RoomTestActivity :AppCompatActivity() {lateinit var viewModel:RoomViewModel
  override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
      ...
      viewModel=ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(RoomViewModel::class.java) ... }}Copy the code

If the ViewModel is inherit AndroidViewModel, then through ViewModelProvider create objects, need to specify the ViewModelProvider. AndroidViewModelFactory created.

How is the ViewModel created

Val Model =ViewModelProvider(this).get(MyViewModel::class.java) So let’s take a look at how the ViewModel is created.

2.1 Create the ViewModelProvider first

Let’s take a look at the ViewModelProvider(this) code

private final Factory mFactory;
private final ViewModelStore mViewModelStore;

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
  mFactory = factory;
  mViewModelStore = store;
}
Copy the code
  • The constructor of our method, ViewModelProvider, takes the argument ViewModelStoreOwner, And we realize that our Activity inherits ComponentActivity and ComponentActivity just happens to implement ViewModelStoreOwner

    public class ComponentActivity extends androidx.core.app.ComponentActivity implements
          LifecycleOwner,
          ViewModelStoreOwner,
          SavedStateRegistryOwner,
          OnBackPressedDispatcherOwner {
          ...
    }
    Copy the code
  • Owner.getviewmodelstore () ComponentActivity getViewModelStore() ComponentActivity 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.");
          }
          if (mViewModelStore == null) {
              NonConfigurationInstances nc =
                      (NonConfigurationInstances) getLastNonConfigurationInstance();
              if(nc ! =null) {
                  // Restore the ViewModelStore from NonConfigurationInstances
                  mViewModelStore = nc.viewModelStore;
              }
              if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); }}return mViewModelStore;
    }
    Copy the code

    And what we found here is that we’re essentially returning a ViewModelStore object. If NonConfigurationInstances viewModelStore there would use NonConfigurationInstances storage, if there is no new one. Specific see NonConfigurationInstances is what

    static final class NonConfigurationInstances {
          Object custom;
          ViewModelStore viewModelStore;
    }
    Copy the code

    NonConfigurationInstances is a static object, which contains a ViewModelStore, we take a look at ViewModelStore

    public class ViewModelStore {
      private final HashMap<String, ViewModel> mMap = new HashMap<>();
      final void put(String key, ViewModel viewModel) {
          ViewModel oldViewModel = mMap.put(key, viewModel);
          if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
          return mMap.get(key);
      }
      Set<String> keys() {
          return new HashSet<>(mMap.keySet());
      }
      public final void clear() {
          for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code

    The ViewModelStore actually has a HashMap inside of it that stores the viewModel, the key is String, the value is viewModel. So to summarize: owner.getViewModelStore() creates a ViewModelStore object.

  • owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : If the owner is HasDefaultViewModelProviderFactory NewInstanceFactory. GetInstance (), Then call HasDefaultViewModelProviderFactory getDefaultViewModelProviderFactory () method, otherwise, they call NewInstanceFactory. GetInstance () method. Because there is no find HasDefaultViewModelProviderFactory place, so we actually code is NewInstanceFactory getInstance () specific code

      static NewInstanceFactory getInstance() {
             if (sInstance == null) {
                 sInstance = new NewInstanceFactory();
             }
             return sInstance;
      }
    Copy the code

    So what we see is a new NewInstanceFactory object.

What does the ViewModelProvider(this) do

  • Create a ViewModeStore, create a Factory, maybe create a NewInstanceFactory depending on the type
  • Assign ViewModeStore to the global mViewModelStore and copy the created Factory to the global mFactory

2.2 create ViewModel

Get (MyViewModel::class.java) is the ViewModelProvider’s get method.

private static final String DEFAULT_KEY ="androidx.lifecycle.ViewModelProvider.DefaultKey";

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);
}
Copy the code

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)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if(viewModel ! =null) {
                // TODO: log a warning.}}if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
}
Copy the code
  • ViewModel viewModel = mViewModelStore.get(key); We can see that we get the ViewModel from the ViewModelStore
  • Key is DEFAULT_KEY:+ the name of the viewmode passed in
  • ViewModel = (mFactory).create(modelClass); Factory create method to create. So let’s look at the create method of the NewInstanceFactory
    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); }}Copy the code

    Through modelClass. NewInstance (); New a ViewModel object directly by reflection.

  • mViewModelStore.put(key, viewModel); After creation, the key-value pair is stored in the ViewModelStore.

How does the ViewModel keep data unchanged when an Activity destroys or rebuilds an exception

The ViewModel is stored in the ViewModelStore, and every Activity has a ViewModelStore. If the ViewModelStore is not recreated, the ViewModel will not be recreated. Through the above and we know that ViewModelStore is stored in the NonConfigurationInstances class, NonConfigurationInstances is ComponentActivity in a static inner class, When the storage to NonConfigurationInstances in the class? First, the state must be preserved so that it can be retrieved during reconstruction. Since you want to save the state, you must save it while onDestroy(), so let’s look at ActivityThread$performDestroyActivity().

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
          ...
            if (getNonConfigInstance) {
                try {
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    if(! mInstrumentation.onException(r.activity, e)) {throw new RuntimeException(
                                "Unable to retain activity "
                                + r.intent.getComponent().toShortString()
                                + ":"+ e.toString(), e); }}}try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnDestroy(r.activity);
                if(! r.activity.mCalled) {throw new SuperNotCalledException(
                        "Activity " + safeToComponentShortString(r.intent) +
                        " did not call through to super.onDestroy()");
                }
                if(r.window ! =null) { r.window.closeAllPanels(); }}catch (SuperNotCalledException e) {
                throw e;
            } catch (Exception e) {
                if(! mInstrumentation.onException(r.activity, e)) {throw new RuntimeException(
                            "Unable to destroy activity " + safeToComponentShortString(r.intent)
                            + ":"+ e.toString(), e); } } r.setState(ON_DESTROY); }...return r;
    }
Copy the code

The key code state Richard armitage ctivity. RetainNonConfigurationInstances (); Let’s see

NonConfigurationInstances retainNonConfigurationInstances() {
        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

OnRetainNonConfigurationInstance this method in ComponentActivity implementation is as follows:

public final Object onRetainNonConfigurationInstance() {
        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 == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
}
Copy the code

So we nci.viewModelStore = viewModelStore; See ViewModelStore is in this place is stored in the NonConfigurationInstances objects. So when will the stored data be used again? Let’s look at the Attach method for our Activity.

public finalActivity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken) { ActivityClientRecord r = new ActivityClientRecord(); . r.lastNonConfigurationInstances = lastNonConfigurationInstances; .return performLaunchActivity(r, null /* customIntent */);
}
 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); .return activity;
    }
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,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
       ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if(voiceInteractor ! =null) {
            if(lastNonConfigurationInstances ! =null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this.this, Looper.myLooper()); }}... }Copy the code

We see stored in ActivityClientRecord NonConfigurationInstances object, and then restart the Activity onAttach () method will be NonConfigurationInstances back, Thus the data is not lost.

Four: when is the ViewModel recycled

We can see that when we create ComponentActivity, we add an Observer for Lifecycle. As follows:

public ComponentActivity() {
       	...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); . }Copy the code

ON_DESTROY, onDestroy is called getViewModelStore().clear(); Methods. GetViewModelStore (ViewModeStore,clear)

public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
}
Copy the code

It’s essentially going through the map, clearing out the data.

Five: The ViewModel lifecycle

The ViewModel object exists for a time Lifecycle that is passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel will remain in memory until Lifecycle is gone permanently: for an Activity, when the Activity completes; For fragments, this is when the Fragment is separating. The following is a diagram from the official website, so you can see the life cycle clearly