Open the official website, we can see the description of the ViewModel:

The ViewModelclass allows data to survive configuration changes such as screen rotations.

The ViewModel lifecycle diagram is also given:



When screen rotation occurs, the ViewModel instance still exists

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        Log.e("TAG", "onCreate:$mainViewModel")
 }
  override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        Log.e("TAG", "onSaveInstanceState:")
 }

 override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        Log.e("TAG", "onRestoreInstanceState:")
 }
Copy the code

If the ViewModel instance is still the instance before the Activity rotates the screen, the output is as follows:

2019-08-13 21:45:55.381  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.435  E/TAG: onSaveInstanceState:
2019-08-13 21:46:06.608  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.617  E/TAG: onRestoreInstanceState:
Copy the code

From the logs, we can see that when screen rotation occurs, the memory address of MainViewModel before rotation is consistent with that of MainViewModel after rotation, which verifies the correctness of the description of ViewModel on the official website.

As we know, when the screen rotates, the entire Activity is destroyed and rebuilt, and its corresponding objects and variables are reinitialized, but the ViewModel instance remains intact. Is the ViewModel instance held by a variable outside the Activity? With that in mind let’s trace the ViewModel source code to see how this is done.

Analysis of the

Initialize the ViewModel

Let’s look at the initialization of a ViewModel object:

val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

1. Trace viewModelProviders.of

public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    / / key point: activity. GetViewModelStore ()
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
Copy the code

Default will initialize a factory, the factory is a reflection to initialize MainViewModel: : class. Java factory class, key points we later again, Initialization of the ViewModelProvider requires passing in the Activity’s ViewModelStore and Factory.

2, tracking the get (MainViewModel: : class. Java)

  public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); .return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
  } 

  public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        / / 1.
        ViewModel viewModel = mViewModelStore.get(key);
		/ / 2,
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return(T) viewModel; }...if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            / / 3.
            viewModel = (mFactory).create(modelClass);
        }
        / / 4.
        mViewModelStore.put(key, viewModel);
        / / 5,
        return (T) viewModel;
   }
Copy the code

Serial number Explanation:

  1. Retrieves the ViewModel from the Activity’s ViewModelStore based on the key
  2. Check whether the retrieved ViewModel is the same as the ViewModel instance of GET. If so, return the ViewModel object directly
  3. Initialize the ViewModel object with factory reflection
  4. Store the ViewModel in the Activity’s ViewModelStore
  5. Return the ViewModel object

From the perspective of the whole process, the ViewModelStore object is very important. The storage and retrieval of the ViewModel are related to it. The Activity destroys and rebuilds the ViewModel instance from the ViewModelStore, and this instance is always the same object.

Get ViewModelStore

1, we talk about the key points for: activity. GetViewModelStore ()

FragmentActivity.class

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
} 

public ViewModelStore getViewModelStore(a) {
        / / 1.
        if (mViewModelStore == null) {
            / / 2,
            NonConfigurationInstances nc =
                     / / 3.
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            / / 3.
            if (mViewModelStore == null) {
                mViewModelStore = newViewModelStore(); }}return mViewModelStore;
 }
Copy the code

Serial number Explanation:

  1. Check whether the global variable mViewModelStore is empty
  2. NonConfigurationInstances FragmentActivity is a static inner class
  3. Get NonConfigurationInstances object
  4. If viewModelStore is still empty, a viewModelStore is created

2, we’ll look at how getLastNonConfigurationInstance get FragmentActivity. NonConfigurationInstances

Activity.class

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
} 

public Object getLastNonConfigurationInstance(a) {
        returnmLastNonConfigurationInstances ! =null
                ? mLastNonConfigurationInstances.activity : null;
 }
Copy the code

MLastNonConfigurationInstances is the Activity of a static inner class called NonConfigurationInstances.

3, we’ll look at how mLastNonConfigurationInstances initialization

Activity.class

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) {... mLastNonConfigurationInstances = lastNonConfigurationInstances; . }Copy the code

When the Activity attach is passed in, who triggers the attach? As we know, the entire creation of an Activity is handled by the ActivityThread.

Let’s take a look at ActivityThread

We find the place that triggers activity.attach ()

ActivityThread.class

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ActivityInfo aInfo = r.activityInfo; . 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); . mActivities.put(r.token, r); . }Copy the code

We noticed that r.l astNonConfigurationInstances this object, the object is to remove the ActivityClientRecord, The ActivityClientRecord is also stored in the mActivities global variable of the ActivityThread.

5, Let’s take a look at the ActivityClientRecord from the following simple call chain:

performLaunchActivity -> handleLaunchActivity -> handleRelaunchActivityInner -> handleRelaunchActivity

Finally we come to the handleRelaunchActivity method:

  @Override
  public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {...//1. Obtain the corresponding ActivityClientRecord using the Activity tokenActivityClientRecord r = mActivities.get(tmp.token); . handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig,"handleRelaunchActivity"); . }Copy the code

Finally, we find the source of the ActivityClientRecord, so the whole chain of calls to get the ViewModelStore comes out:

Let’s go back to the Activity’s getViewModelStore method, which only goes through the call chain if the ViewModelStore is empty. The idea is to avoid the problem of constantly going through the call chain to get the ViewModel, but after rebuilding, The ViewModelStore is definitely empty, so there’s got to be a place where you just take the chain of calls once and get the ViewModelStore object, and the result is in the onCreate method

FragmentActivity.class

 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		
        NonConfigurationInstances nc =
                // Get the ViewModelStore object for the call chain
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null&& nc.viewModelStore ! =null && mViewModelStore == null) { mViewModelStore = nc.viewModelStore; }...Copy the code

The source of the fetch is in the mActivities of the ActivityThread, so the store must also be in the mActivities.

Storage ViewModelStore

Find references in the ActivityThread using mactivities. get:

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); .if (getNonConfigInstance) {
         try {
             / / 1.
             r.lastNonConfigurationInstances
                 / / 2,
                 = r.activity.retainNonConfigurationInstances();
         } catch(Exception e) { ... }}...Copy the code

Serial number Explanation:

  1. When there is Activity attach incoming Activity. NonConfigurationInstances object
  2. Access to the Activity. NonConfigurationInstances object

If you look at the method carefully, it turns out to be performDestroyActivity. That is, before rebuilding the Activity, the ViewModelStore will be saved to the ActivityClientRecord. The ActivityClientRecord is stored in the global mActivities collection of the ActivityThread. After rebuilding, retrieve the ActivityClientRecord from the mActivities. The ViewModelStore is then passed in via the Attach method of the Activity, which confirms our demo and the official sample diagram, But we have to look at the state Richard armitage ctivity. RetainNonConfigurationInstances (); How is it stored

1, the activity. RetainNonConfigurationInstances

Activity.class

 NonConfigurationInstances retainNonConfigurationInstances(a) {
        / / 1.Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); . NonConfigurationInstances nci =newNonConfigurationInstances(); nci.activity = activity; nci.children = children; nci.fragments = fragments; nci.loaders = loaders; .return nci;
    }
Copy the code

RetainNonConfigurationInstances is a creation Activity. NonConfigurationInstances process, according to the above call chain diagram, Activity. NonConfigurationInstances will quote FragmentActivity. NonConfigurationInstances object, Citation in serial number 1 onRetainNonConfigurationInstance method

2, onRetainNonConfigurationInstance

FragmentActivity.class

 @Override
 public final Object onRetainNonConfigurationInstance(a) {... NonConfigurationInstances nci =new NonConfigurationInstances();
        nci.custom = custom;
        / / 1.
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
  }
Copy the code

Here is very clear, FragmentActivity. NonConfigurationInstances will hold mViewModelStore object for storage.

Let’s change the official sample diagram: