1, the ViewModel

1.1 overview of ViewModel

Comments in ViewModel class:

  • ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).

    A ViewModel is a class that prepares and manages the data in an Activity or Fragment. It handles the communication between the Activity and Fragment and the rest of the application.

  • A ViewModel is always created in association with a scope (a fragment or an activity) and will be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a configuration change (e.g. rotation). The new owner instance just re-connects to the existing model.

    A ViewModel is always created with an Activity or Fragment that exists as long as it is alive. The ViewModel is not destroyed by owner configuration programming, and the new Owner example reconnects the existing ViewModel.

  • The purpose of the ViewModel is to acquire and keep the information that is necessary for an Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the ViewModel. ViewModels usually expose this information via LiveData or Android Data Binding. You can also use any observability construct from you favorite framework.

    . , an Activity or Fragment should be able to observe changes in a ViewModel, which usually exposes this information via LiveData,……

  • ViewModel’s only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.

    The ViewModel is only responsible for managing the data in the interface. It should not access views or hold references to activities and fragments.

  • ViewModels can also be used as a communication layer between different Fragments of an Activity. Each Fragment can acquire the ViewModel using the same key via their Activity. This allows communication between Fragments in a de-coupled fashion such that they never need to talk to the other Fragment directly.

    The ViewModel can also serve as a communication layer between different fragments in the same Activity. Each Fragment can use the same key for the Activity to retrieve the ViewModel, which enables the fragments to communicate in a decoupled manner.

1.2. Basic use of ViewModel

  • Custom ViewModel inherit ViewMode, AndroidViewModel inherit AndroidViewModel if Context is needed in ViewModel;
  • Pass in an Activity or Fragmentval vm by viewModels<TVM>()Get the ViewModel instance.

2, ViewModel related source code analysis

2.1 ViewModel instance acquisition

Val VM by viewModels

()

@MainThread public inline fun <reified VM : ViewModel> ComponentActivity.viewModels( noinline factoryProducer: (() -> Factory)? = null ): Lazy<VM> { val factoryPromise = factoryProducer ? : { defaultViewModelProviderFactory } return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise) }Copy the code

ViewModels ComponentActivity is a development method, pass in a Factory Factory as an example, when you don’t pass default defaultViewModelProviderFactory as create ViewModel Factory, return Lazy < > VM type, A ViewModelLazy object is actually created and returned. Kotlin’s attribute delegate is used here. Use reified and inline to inline the method to the call, with generic arguments replaced directly, avoiding passing arguments of Class type.

2.1.1, Factory

Androidx. Lifecycle. ViewModelProvider Factory, is one of ViewModelProvider class interface, is used to create the ViewModel instance

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

2.1.2, Lazy

Interface for deferred initialization

public interface Lazy<out T> {
    public val value: T
    public fun isInitialized(): Boolean
}
Copy the code

2.1.3, ViewModelLazy

The class corresponding to the property delegate, providing getValue and setValue methods (setValue only if the property is var)

public class ViewModelLazy<VM : ViewModel> ( private val viewModelClass: KClass<VM>, private val storeProducer: () -> ViewModelStore, private val factoryProducer: () -> ViewModelProvider.Factory ) : Lazy<VM> { private var cached: VM? = null override val value: VM get() { val viewModel = cached return if (viewModel == null) { val factory = factoryProducer() val store = StoreProducer () // Fetch ViewModelProvider(store, factory).get(viewModelClass.java).also { cached = it } } else { viewModel } } override fun isInitialized(): Boolean = cached ! = null }Copy the code

2.1.4, ViewModelProvider

A utility class that retrieves or generates the corresponding ViewModel from the ViewModelStore.

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

2.2 Destruction of ViewModel

2.2.1 ViewModel clear method

@MainThread final void clear() { mCleared = true; if (mBagOfTags ! = null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // see comment for the similar call in setTagIfAbsent closeWithRuntimeException(value); } } } onCleared(); } / / close all rely on the ViewModel instances of Closeable private static void closeWithRuntimeException (Object obj) {if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); Protected void onCleared() {}Copy the code

The ViewModel clear method does two things:

  • Iterate over the HashMap of mBagOfTags, calling the close method for objects of type Closeable
  • Call the onCleared method to perform the user-expanded cleanup logic

Where mBagOfTags is filled in in the setTagIfAbsent method.

<T> T setTagIfAbsent(String key, T newValue) {
    T previous;
    synchronized (mBagOfTags) {
        previous = (T) mBagOfTags.get(key);
        if (previous == null) {
            mBagOfTags.put(key, newValue);
        }
    }
    T result = previous == null ? newValue : previous;
    if (mCleared) {
        closeWithRuntimeException(result);
    }
    return result;
}
Copy the code

Use AS’s Find Usage to Find where the method was called:

val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope ! = null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) } internal class CloseableCoroutineScope(context:  CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } }Copy the code

This is the extension method in the ViewModel KTX library, and you can see that the extension property we normally use in the ViewModel viewModelScope is actually of type CloseableCoroutineScope, And its close method cancels all jobs, so the coroutine we started in the ViewModel with the viewModelScope is automatically cancelled when the ViewModel is destroyed.

2.2.2 clear method call timing

Also use AS Find Usage to Find that the method is called in ViewModelStore clear method, further upward search, found in ComponentActivity called ViewModelStore clear method. In its constructor there is the following code:

GetLifecycle ().addobServer (new LifecycleEventObserver() {@override public void getLifecycle() onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { mContextAwareHelper.clearAvailableContext(); // No configuration changes have occurred, and all associated ViewModel if (! isChangingConfigurations()) { getViewModelStore().clear(); }}}});Copy the code

2.3. Restore the ViewModel when the Activity configuration changes

2.3.1, save,

When a configuration change is made, the current Activity is destroyed and rebuilt. Activity destruction goes to the performDestroyActivity method in the ActivityThread. This method calls the Activity retainNonConfigurationInstances method, and returns the results to the ActivityClientRecord objects.

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (r ! = null) { activityClass = r.activity.getClass(); r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; } performPauseActivityIfNeeded(r, "destroy"); if (! r.stopped) { callActivityOnStop(r, false /* saveState */, "destroy"); {} the if (getNonConfigInstance) try {/ / get NonConfigurationInstances r.l astNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { } } // ...... return r; }Copy the code

Activity retainNonConfigurationInstances, first calls onRetainNonConfigurationInstance method, this method is an empty method in the Activity, This method is implemented in ComponentActivity. After creating the activityNonConfigurationInstances instance and onRetainNonConfigurationInstance return values assigned to the members of the activity.

NonConfigurationInstances retainNonConfigurationInstances () {/ / 1, the first calls onRetainNonConfigurationInstance Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); mFragments.doLoaderStart(); mFragments.doLoaderStop(true); ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } / / 2, create NonConfigurationInstances object and assignment 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

ComponentActivity defines its own internal NonConfigurationInstances and will save mViewModelStore examples.

Public final Object onRetainNonConfigurationInstance () {/ / 1, save the contents of a custom Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! = null) { viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } / / 2, save viewModelStore NonConfigurationInstances nci = new NonConfigurationInstances (); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; }Copy the code

Follow the invocation path down, at the time of the Activity destroyed, will save ViewModelStore to NonConfigurationInstances, This instance is saved in the ActivityThread’s ActivityClientRecord.

2.3.1, restore

We can see from the saving process, we save the ViewModelStore object, and ViewModel is saved in ViewModelStore, ComponentActivity implements the ViewModelStoreOwner interface, The getViewModelStore method is used to retrieve the ViewModelStore, so the reply process should be implemented within that method.

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.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}
Copy the code

EnsureViewModelStore is called directly and the mViewModelStore member is returned, so mViewModelStore should be initialized within the ensureViewModelStore method.

Void ensureViewModelStore () {if (mViewModelStore = = null) {/ / 1, obtain NonConfigurationInstances () in ComponentActivity NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); Object if (nc! MViewModelStore = nc. ViewModelStore; If (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); }}}Copy the code

Inside can be seen by capturing the getLastNonConfigurationInstance ComponentActivity NonConfigurationInstances type, and then to take viewModelStore from it. Here, we will understand how getLastNonConfigurationInstance get instance.

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

GetLastNonConfigurationInstance is methods on the Activity class, when mLastNonConfigurationInstances attribute is not null, the return type (Object) the Activity field. MLastNonConfigurationInstances is NonConfigurationInstances type of Activity. Search the Activity code, found that mLastNonConfigurationInstances is assigned in the attach method. We know that the Attach method is called in the performLaunchActivity method of the ActivityThread.

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

Can see a call directly delivered the ActivityClientRecord lastNonConfigurationInstances. So where did the ActivityClientRecord come from? We know that attaching the Activity is called in the ActivityThread’s performLaunchActivity method, so we can make the next breakpoint in that method, Find out where the ActivityClientRecord is coming from by looking at the function call stack.

  • Debug Framework source code in AS

    Because the code of the real phone is usually modified by the mobile phone manufacturer, it may not correspond to the debugging, so we choose to create a simulator in AS for debugging. For example, if we need to debug the code for ActivityThread, the SDK version may be different from the version on the debug machine. Therefore, we can download the source code of ActivityThread corresponding to the SDK version of the mobile phone, and then determine the package of ActivityThread (Android.app). Then create a package with the same name and copy the code to this package for debugging.

I have debugged on the Android-5.1 emulator and finally found the ActivityClientRecord in the performRelaunchActivity method with the following code. This is then passed to the handleLaunchActivity method, all the way to the performLaunchActivity method, and finally to the Attach method.

ActivityClientRecord r = mActivities.get(tmp.token); / /... handleLaunchActivity(r, currentIntent); Final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<IBinder, ActivityClientRecord>();Copy the code

As you can see, mActivities are a member variable of the ActivityThread class, so the ActivityClientRecord is stored in the ActivityThread, so the content is not lost as long as the application is not destroyed. (Normal Destroy should remove the corresponding ActivityClientRecord from it).