current position:Home>ViewModel of jetpack

ViewModel of jetpack

2022-01-27 03:50:21 LiuShangYuan

1、ViewModel

1.1、ViewModel summary

ViewModel Comments in 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).

    ViewModel It's a tool for preparing and managing Activity or Fragment Class of data in , It handles Activity and Fragment Communication with other parts 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.

    ViewModel Always create with a Activity perhaps Fragment relation , As long as they are alive ,ViewModel There will always be .ViewModel Not because owner Configuration programming and destruction , new owner The 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.

    ......,Activity or Fragment Should be able to observe ViewModel Changes ,ViewModel Usually by LiveData To expose this information ,......

  • 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.

    ViewModel Only responsible for managing the data of the interface , It should not access views or hold Activity and Fragment References to .

  • 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.

    ViewModel It can also be used as the same Activity Different from Fragment Communication layer between , Every Fragment Can pass Activity Use the same key To get ViewModel, This makes Fragment They communicate through decoupling .

1.2、ViewModel Basic use

  • Customize ViewModel Inherit ViewMode, If ViewModel Required in Context Then inherit AndroidViewModel;
  • stay Activity perhaps Fragment Pass through val vm by viewModels<TVM>() obtain ViewModel example .

2、ViewModel Related source code analysis

2.1、ViewModel Instance acquisition

val vm by viewModels<VM>() What did you do ?

@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 code 

viewModels yes ComponentActivity An extension of , Pass in a Factory Factory example , When not transmitted, it defaults to defaultViewModelProviderFactory As the creation of ViewModel Our factory , return Lazy<VM> type , Actually created a ViewModelLazy Object and return .
It's used here Kotlin Property delegate for . Use reified and inline Inline the method to the caller , Generic parameters are replaced directly , Avoid passing Class Parameters of type .

2.1.1、Factory

androidx.lifecycle.ViewModelProvider.Factory, yes ViewModelProvider Class , Used to create ViewModel example

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

2.1.2、Lazy

Interface , Used to delay initialization

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

2.1.3、ViewModelLazy

Attribute delegate corresponding class , Provide getValue and setValue Method ( When the property is var Only when setValue)

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()
                //  adopt  ViewModelProvider  selection 
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}
 Copy code 

2.1.4、ViewModelProvider

It is equivalent to a tool class , from ViewModelStore Take out or generate the corresponding ViewModel.

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 code 

2.2、ViewModel Destruction of

2.2.1、ViewModel Of 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 dependencies  ViewModel  Of  Closeable  example 
private static void closeWithRuntimeException(Object obj) {
    if (obj instanceof Closeable) {
        try {
            ((Closeable) obj).close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
//  Provide users with their own  ViewModel  The time to release and clean up relevant resources 
protected void onCleared() {
}
 Copy code 

ViewModel Of clear There are two main things done in the method :

  • Traverse mBagOfTags This HashMap, Will the Closeable Object call of type close Method
  • call onCleared Method to execute the cleaning logic of user expansion

among mBagOfTags stay setTagIfAbsent Method is filled with .

<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 code 

adopt AS Of Find Usage Find the location where the method is called and find the following code :

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 code 

This is a ViewModel ktx Expansion method in Library , You can see that we usually ViewModel Extended properties used in viewModelScope The actual type is CloseableCoroutineScope, And its close Method cancels all Job, So we have ViewModel In the use of viewModelScope The start-up process will be in ViewModel Automatically cancel on destruction .

2.2.2、clear Method call timing

Using the same AS Of Find Usage Find the method in ViewModelStore Of clear Method is called , Look up further , Found in ComponentActivity Called ViewModelStore Of clear Method . In its construction method, there is the following code :

//  Register an observer for a lifecycle event 
getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            mContextAwareHelper.clearAvailableContext();
            //  No configuration changes have occurred ,  Clean up all associated ViewModel
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});
 Copy code 

2.3、Activity When the configuration changes ViewModel The recovery of

2.3.1、 preservation

When the configuration changes, the current... Will be destroyed first Activity Then rebuild ,Activity Destruction will come ActivityThread Medium performDestroyActivity In the method , This method calls Activity Of retainNonConfigurationInstances Method , And save the returned results to ActivityClientRecord In the object .

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");
        }
        if (getNonConfigInstance) {
            try {
            //  obtain  NonConfigurationInstances
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
            }
        }
        
        // ......
        
    return r;
}
 Copy code 

Activity Of retainNonConfigurationInstances in , First, I call onRetainNonConfigurationInstance Method , The method in Activity Is an empty method , stay ComponentActivity The method is implemented in . And then created activityNonConfigurationInstances example , And will onRetainNonConfigurationInstance The return value of is assigned to the member activity.

NonConfigurationInstances retainNonConfigurationInstances() {
    // 1、 First call  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、 establish  NonConfigurationInstances  Object and assign values 
    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 code 

ComponentActivity Will define their own internal NonConfigurationInstances, And will mViewModelStore Save the instance .

public final Object onRetainNonConfigurationInstance() {
    // 1、 Customized saved content 
    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、 preservation  viewModelStore
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
 Copy code 

Follow the call path , stay Activity At the time of destruction , Will ViewModelStore Save to NonConfigurationInstances in , And the instance will be saved in ActivityThread Of ActivityClientRecord in .

2.3.1、 recovery

From the preservation process , What we keep is ViewModelStore object , and ViewModel It is kept in ViewModelStore in ,ComponentActivity Realized ViewModelStoreOwner Interface , Its getViewModelStore The method is to get ViewModelStore Of , Therefore, the process of reply should be implemented in this 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 code 

Call directly ensureViewModelStore Then return mViewModelStore member , therefore ensureViewModelStore It should be done in the method mViewModelStore The initialization .

void ensureViewModelStore() {
    if (mViewModelStore == null) {
        // 1、 obtain  NonConfigurationInstances (ComponentActivity  Medium )
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance(); //  The return is Object
        if (nc != null) {
            // 2、 Get the  ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        // 3、 No access to , Corresponding to first creation , direct new One 
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}
 Copy code 

You can see through getLastNonConfigurationInstance Got it ComponentActivity Inside NonConfigurationInstances type , Then take... From it viewModelStore. Come here , We need to understand getLastNonConfigurationInstance How to get the instance .

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

getLastNonConfigurationInstance yes Activity Methods in class , When mLastNonConfigurationInstances Property is not null when , return activity Field (Object) type .mLastNonConfigurationInstances yes Activity Medium NonConfigurationInstances type .
Search for Activity Code for , Find out mLastNonConfigurationInstances Is in attach Method . We know attach The method is in ActivityThread Of performLaunchActivity Called in method .

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 code 

You can see that... Is passed directly when calling ActivityClientRecord Of lastNonConfigurationInstances. So here ActivityClientRecord Where did it come from . We know activity Of attach Is in ActivityThread Of performLaunchActivity Called in the method , So we can find the next breakpoint in this method , Find... By looking at the function call stack ActivityClientRecord Where did it come from .

  • AS In the middle of Framework Source debugging

    Because the code on the real machine is usually modified by the mobile phone manufacturer , Debugging may not correspond to , So here we choose to use in AS Create an emulator in to debug . For example, we need to debug ActivityThread Corresponding code , Rely on the SDK The version may be inconsistent with the version on the debugging machine . So we can download the corresponding mobile phone SDK Version of ActivityThread Source code , Then make sure ActivityThread Of package(android.app); Then create a file with the same name package, Then copy the code to the package Debugging can be carried out under .

I am Android-5.1 It is debugged on the simulator , Finally found in performRelaunchActivity Method through the following code ActivityClientRecord. Then pass it on to handleLaunchActivity Method , All the way to performLaunchActivity In the method , Finally, it was passed on attach In the method .

ActivityClientRecord r = mActivities.get(tmp.token);
// ......
handleLaunchActivity(r, currentIntent);

// mActivities The definition of 
final ArrayMap<IBinder, ActivityClientRecord> mActivities
    = new ArrayMap<IBinder, ActivityClientRecord>();
 Copy code 

You can see mActivities yes ActivityThread A member variable of class , therefore ActivityClientRecord Is stored in the ActivityThread Medium , So as long as the application is not destroyed , This content will not be lost .( normal Destroy The corresponding... Should be removed from it ActivityClientRecord).

copyright notice
author[LiuShangYuan],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201270350108125.html

Random recommended