Android Jetpack provides viewModels for maintaining data needed in activities or fragments. It can maintain internal data even when activities and fragments are rebuilt due to configuration changes, such as screen rotation.

background

First of all, according to the official document can be obtained ViewModel applicable scenarios (also can be called the purpose of appearance) :

  • When a configuration change occurs, activities and fragments are destroyed and rebuilt, and their internal ephemeral data (data that was not passed in through the Intent) is lost. If you put this temporary data into the ViewModel, you can avoid data loss. Of course you can use itonSaveInstanceStateTo retain temporary data, but if the amount of temporary data is large,onSaveInstanceStateBecause cross-process communication is involved, larger data volumes can resultmarshallingunmashllingLarge consumption. There is no cross-process communication overhead with the ViewModel. But it didn’tonSaveInstanceStateProvides the ability to recover data after an Activity is reclaimed: The system reclaims the Activity when it is in the background and when it returns to the foreground, the system reclaims the ActivityonSaveInstanceStateThe saved data inonRestoreInstanceStateonCreateIn thesavedInstanceStateParameters are passed to the Activity.
  • ViewModel, as its name implies, is essentially used to store view-related data. It is officially recommended to extract data and data operations in activities (Fragment) into ViewModel, so that the display control of the view and data separation. It feels like MVP mode with the ViewModel.
  • The ViewModel can sense changes in the Activity or Fragment’s life cycle and perform some data cleaning when the Activity or Fragment is destroyed. (The implementation class of the ViewModel can be overriddenonClearedMethods).

First a showdown

The following implementation principles of the search may be more content, may make the reader feel a bit of procrastination, so first throw out the results found through the implementation principles of the search.

I summarize the realization principle of ViewModel as follows: Beat the cow from the hill

  • The ViewModels of an Activity(Fragment) are stored in the ViewModelStore, and each Activity(Fragment) has an instance of the ViewModelStore
  • The ViewModelProvider provides users with an interface to access a ViewModel. It holds the ViewModelStore of the current Activity(Fragment) and then delegates operations to the ViewModelStore
  • A ViewModel can be used to restore data when an Activity(Fragment) is being rebuilt due to configuration. The Activity(referring to ComponentActivity in the Support Library) gives the ViewModelStore to the ActivityThread before the Activity(Fragment) is rebuilt ActivityClientRecord holds the ViewModelStore from ActivityClientRecord after the Activity(Fragment) is rebuilt
  • If the application process is in the background, it is destroyed due to lack of system memory. Even those using viewModels cannot recover data during Activity(Fragment) reconstruction. Because the ViewModelStore that stores the ViewModel is temporarily given to the ActivityClientRecord in the ActivityThread, the process is reclaimed and the ActivityThread is reclaimed, The ViewModelStore is reclaimed, and the ViewModel doesn’t exist anymore

Analysis of the entrance

JetPack provides components that are packaged based on existing components of the Android SDK, without modifying those classes already in the SDK. The ViewModel, too, is implemented based on ComponentActivity in AndroidX(formerly the Support Library).

Combined with the function characteristics of ViewModel, I will start from the following aspects, and then clarify the realization principle of ViewModel.

  1. Classes and data structures involved in the ViewModel.
  2. How does the ViewModel ensure that the data in the ViewModel is preserved when an Activity or Fragment is rebuilt due to configuration changes?

Core data structure

Core Data Structures introduce some of the classes and data structures involved in the ViewModel.

ViewModel

The ViewModel is an abstract class that users need to inherit from, with fewer variables and methods inside.

private volatile boolean mCleared = false;

Indicates whether the current ViewModel has been destroyed.

protected void onCleared()

Subclasses override this method to perform additional operations (such as freeing resources, etc.) when the ViewModel is destroyed.

final void clear()

ViewModelStore

The ViewModelStore is, as the name suggests, a class that is responsible for storing viewModels. A quote from the ViewModelStore code comment indicates what it does:

The ViewModelStore instance must be preserved in the event of a configuration change: If the owner of this ViewModelStore is destroyed and recreated due to a configuration change, the new instance of the owner should have the same old ViewModelStore instance.

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

MMap is the only and only member variable in ViewModelStore, as evidenced by its generic type parameter. It is the pool that ViewModelStore uses to store viewModels.

final void put(String key, ViewModel viewModel)

The ViewModel is stored in the ViewModelStore pool. If the pool already has a key corresponding to the ViewModel, the old ViewModel is replaced by the new one and the onCleared method of the old ViewModel is called. (shouldn’t 🤔️ call ViewModel clear to release resources?)

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

ViewModel get(String key)

Get the ViewModel for the key from the pool (mMap)

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

Set<String> keys()

Returns all keys of mMap

final void clear()

Clear all viewModels in mMap and call the clear method on each one

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

ViewModelStore’s get and PUT methods are package-level, which means that users cannot directly access the corresponding ViewModel via the ViewModelStore key.

ViewModelProvider

A utility class that provides ViewModels for scopes (Application, Activity, Fragment)

Let’s take a look at the ViewModelProvider fields:

private final Factory mFactory;

public interface Factory {
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
Copy the code

Factory indicates the Factory in which the ViewModel was created, and the declaration of final indicates that it must be assigned in the constructor of the ViewModelProvider.

final ViewModelStore mViewModelStore;

The declaration of final means that mViewModelStore must also be assigned in the constructor.

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

DEFAULT_KEY, used to construct the key when submitting the ViewModel to the ViewModelStore

Here are the methods in the ViewModelProvider:

A constructor

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

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}
Copy the code

public T get(@NonNull Class modelClass) public T get(@NonNull String key, @NonNull Class modelClass)

These two methods are the methods that get the ViewModel, and are the two public interfaces that the ViewModel library provides to the consumer. The first get method constructs a key using DEFAULT_KEY, and then calls the second GET method.

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    // Build a key
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
Copy the code
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    // Get the ViewModel from the ViewModelStore
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    } else {
        if(viewModel ! =null) {}}// Create a ViewModel without a corresponding ViewModel in ViewModelStore
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    // Store the created ViewModel in the ViewModelStore
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}
Copy the code

ViewModelStoreOwner

ViewModelStoreOwner is an interface that declares a getViewModelStore method that needs to be implemented by the class.

The responsibility of the class implementing this interface is to keep its own ViewModelStore from being destroyed during configuration changes.

public interface ViewModelStoreOwner {

    @NonNull
    ViewModelStore getViewModelStore(a);
}
Copy the code

ComponentActivity

ComponentActivity is added to the AndroidX. Activity package, and FragmentActivity inherits from it.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory.SavedStateRegistryOwner.OnBackPressedDispatcherOwner
Copy the code

There are two variables associated with ViewModel in ComponentActivity:

ViewModelStore mViewModelStore;

Represents the ViewModelStore of the current Activity. An Activity has a ViewModelStore

ViewModelProvider.Factory mDefaultFactory;

Create ViewModel factory implementation, is used in the ComponentActivity SavedStateViewModelFactory, SavedStateViewModelFactory is associated with the reconstruction of the Activity.

ViewModelStore getViewModelStore();

Implemented in ComponentActivity

public ViewModelStore getViewModelStore(a) {
    // If mViewModelStore is not already assigned, create an instance to assign to it
    if (mViewModelStore == null) {
        // Relates to data recovery during Activity rebuilding, more on that later
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null) {
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            // Create ViewModelStore and assign it to the mViewModelStore variable for ComponentActivity
            mViewModelStore = newViewModelStore(); }}return mViewModelStore;
}
Copy the code

The creation of ViewModelStore

ViewModelStore is responsible for storing viewModels, so generally you need to have a container before you can store viewModels. And the ViewModelStore is provided by the ViewModelStoreOwner.

I look at the call to the method stack at the break point of the getViewModelStore method of ViewModeStoreOwner. After a few breakpoints, I noticed that the creation times of FragmentActivity and non-FragmentActivity are different, so I’ll keep them separate.

FragmentActivity

Look at the FragmentActivity call stack:

getViewModelStore:262, ComponentActivity (androidx.activity)
getViewModelStore:887, FragmentActivity$HostCallbacks (androidx.fragment.app)
attachController:3022, FragmentManager (androidx.fragment.app)
attachHost:116, FragmentController (androidx.fragment.app)
onCreate:283, FragmentActivity (androidx.fragment.app)
onCreate:18, ViewModelActivity
performCreate:7136, Activity (android.app)
performCreate:7127, Activity (android.app)
callActivityOnCreate:1271, Instrumentation (android.app)
performLaunchActivity:2893, ActivityThread (android.app)
handleLaunchActivity:3048, ActivityThread (android.app)

You can see from the call stack that a FragmentActivity calls getViewModelStore on onCreate.

As long as an Activity inherits from FragmentActivity (for example: AppCompatActivity) will create an instance of ViewModelStore on onCreate and reference it with its own mViewModelStore variable.

So why does FragmentActivity need to create a ViewModelStore at onCreate?

If you look at the call stack, you can see that the getViewModelStore method is called within the attachController method of the FragmentManager:

void attachController(@NonNull FragmentHostCallback
        host, @NonNull FragmentContainer container) {
    mHost = host;
    // Get the FragmentManagerViewModel
    if(parent ! =null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        // Fragments without nested fragments go here
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false); }}Copy the code
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
    / / create FragmentManagerViewModel viewModelProvider get internal will add the ViewModel to ViewModelStore
    ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore, FACTORY);
    return viewModelProvider.get(FragmentManagerViewModel.class);
}
Copy the code

The reason was found: The FragmentActivity calls getViewModelStore in onCreate to allow the FragmentManagerViewModel to be added to the Activity’s ViewModelStore as soon as possible.

Other Activity

An Activity that does not inherit from FragmentActivity will not create a ViewModelStore in onCreate, but will wait until it is useful. For example, if I post a Runnable to my Activity onCreate, and then use the ViewModelProvider to retrieve the ViewModel as described in the ViewModel document:

runOnUiThread {
    val mMode = ViewModelProvider(this).get(MMode::class.java)
}
Copy the code

The stack after execution is:

getViewModelStore:262, ComponentActivity (androidx.activity)
<init>:94, ViewModelProvider (androidx.lifecycle)
run:20, ViewModelActivity$onCreate$1 
runOnUiThread:6282, Activity (android.app)


So ViewModelStore creation timing differs between fragmentActivity-based activities and ComponentActivity-based activities. On FragmentActivity it is created in onCreate; On ComponentActivity it is created when the ViewModel is retrieved using the ViewModelProvider.

ViewModelStore is basically a member field in ComponentActivity, and it’s only created once.

Look at the ViewModelProvider, which is really just a facade to access the ViewModelStore, and it doesn’t store any data internally. So every time you get a ViewModel, you create a new ViewModelProvider instance.

The rebuild of the ViewModel remains

For the ViewModel to perform the same functions as onSaveInstanceState, it needs to retain data inside the Activity(or Fragment) when it is destroyed and restore it when it is rebuilt.

As mentioned earlier, the libraries in Jetpack are packaged based on existing SDK implementations and do not modify existing SDK implementations. For the ViewModel to implement the function of rebuilding reservations, it must have an opportunity to do the reservation action. OnSaveInstanceState onSaveInstanceState onSaveInstanceState onSaveInstanceState onSaveInstanceState onSaveInstanceState onSaveInstanceState onSaveInstanceState

protected void onSaveInstanceState(@NonNull Bundle outState) {
    Lifecycle lifecycle = getLifecycle();
    if (lifecycle instanceof LifecycleRegistry) {
        ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
    }
    super.onSaveInstanceState(outState);
    mSavedStateRegistryController.performSave(outState);
}
Copy the code

There is nothing associated with the ViewModel, so the path is blocked. MViewModelStore (mViewModelStore, mViewModelStore, mViewModelStore)

Field
    mViewModelStore
Found usages  (6 usages found)
    ComponentActivity  (6 usages found)
        onRetainNonConfigurationInstance()  (1 usage found)
            183 ViewModelStore viewModelStore = mViewModelStore;
        getViewModelStore()  (5 usages found)
            266 if (mViewModelStore == null) {
            271 mViewModelStore = nc.viewModelStore;
            273 if (mViewModelStore == null) {
            274 mViewModelStore = new ViewModelStore();
            277 return mViewModelStore;

Found in addition to getViewModelStore, another method is onRetainNonConfigurationInstance (), continue to find the method of use:

Method
    onRetainNonConfigurationInstance()
Found usages  (9 usages found)
    Activity.java  (6 usages found)
        2422 Object activity = onRetainNonConfigurationInstance();
    LocalActivityManager.java  (1 usage found)
        615 Object instance = r.activity.onRetainNonConfigurationInstance();

Activity of retainNonConfigurationInstances method calls the subclass onRetainNonConfigurationInstance () method:

NonConfigurationInstances retainNonConfigurationInstances(a) {
    / / need Activity subclasses implement onRetainNonConfigurationInstance method
    Object activity = onRetainNonConfigurationInstance();

    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

In finding the Activity retaionNonConfigurationInstances method caller, Android Studio shows I can’t find it. Since its caller is ActivityThread(annotated by @hide), go to ActivityThread and search.

The performDestroyActivity of the ActivityThread found the call:

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    // Get the ActivityClientRecord corresponding to the token from mActivities
    ActivityClientRecord r = mActivities.get(token);
    Class<? extends Activity> activityClass = null;
    if(r ! =null) {
        / /...
        // If getNonConfigInstance is true
        if (getNonConfigInstance) {
            try {
                / / will be credited to ActivityClientRecord ViewModelStore lastNonConfiguratinInstances field
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {}
        }
        / /...
    }
    mActivities.remove(token);
    return r;
}
Copy the code

So where is the getNonConfigInstance parameter assigned to true?

Found by locating: When activityThread. H receives the RELAUNCH_ACTIVITY message, it goes to the performDestroyActivity method, And in RELAUNCH_ACTIVITY news consumption chain handleRelaunchActivityInner method will assign getNonConfingInstance value is true.

Immediately after Activity Destroy is executed, the handleLaunchActivity method is executed to rebuild the Activity.

After creating a new object of the Activity, will put lastNonConfigurationInstances written to the field of Activity. Because lastNonConfigurationInstances here there has been a ActivityClientRecord (r) in your code, and in ActivityThread ActivityClientRecord is maintenance, So the destruction and rebuilding of Activity objects does not affect the ActivityClientRecord object in the ActivityThread, This is why the ActivityClientRecord can hold data that is separate from the Activity lifecycle.

Through the above know lastNonConfigurationInstances in ComponentActivity ViewModelStore object is stored in a practical Activity. So the ViewModelStore relies on the ActivityClientRecord to achieve independence from the Activity lifecycle.

There are many libraries in Jetpack that leverage existing mechanisms from the Android SDK to implement certain features, which I think are pretty much figurative.


StefanJi

An Android developer with a wide range of interests 🏀🚴🌲🎬 college

github.com/stefanji