directory

  • 1. Introduction
  • 2. Source analysis
  • 3. Frequently Asked Questions
  • 4. To summarize

One, foreword

Before ViewModel came along, it was a must in Android development to recover data after Activity/Fragment reconstruction and to deal with possible memory leaks. ViewModel is a great way to make this kind of scenario much easier for developers. The ViewModel, an important part of JetPack, is a container for managing data in activities/fragments. It will not be destroyed due to setting changes and will not cause memory leaks.

Second, source code analysis

Let’s see how the ViewModel works before we explain the principles. First, define a ViewModel

class MyViewModel: ViewModel() {val downloadStatus = MutableLiveData<String>() fun updateDownloadStatus() {// Network request and other time-consuming operations downloadStatue.value = "8%" } }Copy the code

Then use the ViewModel class in your Activity.

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // Create a ViewModel the first time the system calls an activity's onCreate() method. // Re-created activities receive the same MyViewModel instance created by the first activity. // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val mViewModel by viewModels() MViewModel. DownloadStatus. Observe (this, the Observer {/ / update the UI updateDownloadButtonStatus (it)})}}Copy the code

Through the above code, you can achieve a memory leak free data acquisition. And when the Activity is rebuilt, the ViewModel still gets the object that was created in the first place, so its saved data is not lost. When an Activity is destroyed, the ViewModel’s onCleared method is automatically called. Clean up the ViewModel object.

1. How is the ViewModel created and why can’t the constructor be called directly? 2. How does the ViewModel save its state after an Activity is rebuilt? 3. Why don’t viewModels need to be destroyed manually?

Let’s dig into the relevant source code to find out. First look at how the ViewModel is created.

ViewModels () is actually an extension method to ComponentActivity. @MainThread public inline fun <reified VM : ViewModel> ComponentActivity.viewModels( noinline factoryProducer: (() -> Factory)? = null ): Lazy VM > < {/ / from ComponentActivity getDefaultViewModelProviderFactory () / / version: Androidx. Activity. The activity: 1.1.0 val factoryPromise = factoryProducer? : {defaultViewModelProviderFactory} / / from ComponentActivity getViewModelStore () return ViewModelLazy (VM: : class, { viewModelStore }, factoryPromise) } 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() {// create ViewModel val ViewModel = cached return if (ViewModel == null) {val Factory = factoryProducer() val store = storeProducer() //ViewModelProvider [1] //ViewModelStore [2] //ViewModelProvider.Factory[3] ViewModelProvider(store, factory).get(viewModelClass.java).also { cached = it } } else { viewModel } } override fun isInitialized(): Boolean = cached ! = null }Copy the code

Can see from the code in the process of create ViewModel involves the following several kind of ViewModel, ViewModelProvider, ViewModelProvider. Factory, ViewModelStore, ViewModelStoreOwner: Provides methods to create ViewModel instances externally. ViewModelProvider. Factory: provide a way to create ViewModel ViewModelStore: cache the ViewModel, storage by using hashMap the ViewModel instance ViewModelStoreOwner: As the owner of ViewModelStore, first analyze from the entry class ViewModelProvider.

//ViewModeProvider.java @NonNull @MainThread 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); } @NonNull @MainThread public <T extends ViewModel> T get(@NonNull String key, NonNull Class<T> modelClass) {// Get from ViewModelStore cache. So the state saving of the ViewModel puts the responsibility on the ViewModelStore ViewModel ViewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { //noinspection unchecked return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel ! = null) { // TODO: log a warning. } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass); } else {// Create ViewModel = (mFactory).create(modelClass); // Create ViewModel = (mFactory).create(modelClass); } // Put the ViewModel into the ViewModelStore cache mviewModelStore.put (key, ViewModel); //noinspection unchecked return (T) viewModel; }Copy the code

As you can see from the above code, if we call the ViewModel constructor directly in our code, we will find that the state will not be saved (of course, since each rebuild creates a new ViewModel object). So when we get a ViewModel object, we should create it through the ViewModelProvider class, which handles how the ViewModel is created and the caching strategy. To save the ViewModel object, it is necessary to save and restore the ViewModelStore object when the Activity/Fragment state changes. Let’s go back to ComponentActivity and analyze how the Activity is saved and restored.

//androidx.activity.ComponentActivity public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner, OnBackPressedDispatcherOwner { static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; }... Omit getLifecycle().addobServer (new LifecycleEventObserver() {@override public void onStateChanged(@nonnull) LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (! IsChangingConfigurations ()) {// Clear the ViewModel object getViewModelStore().clear(); }}}}); /** * Retain all appropriate non-config state. You can NOT * override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to * retain your own non config state. */ @Override @Nullable 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 / / which is the last time you receive the return values NonConfigurationInstances onRetainNonConfigurationInstance 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

This code will solve the problem 2 and 3, in ComponentActivity ViewModelStore is done by onRetainNonConfigurationInstance mechanism when saving. Through ViewModelStore into custom NonConfigurationInstances object and return in its methods. The ViewModel object can be saved. And then obtained by getLastNonConfigurationInstance save objects.

Three, common problems

1. How to share data between two Fragments using ViewModel? A: When creating with ViewModelProvider, just pass in the Activity. Because you’re actually passing in a ViewModelStoreOwner, which implements the getViewModelStore method. Therefore, the generated object is stored in the Activity. So fragments in the same Activity get the same ViewModel object. Ex. :

Private val mViewModel by activityViewModels() private val mViewModel by activityViewModels()Copy the code

2. What if you want to share data between activities? A: We essentially share the same ViewModel object between different activities. Judging from the above code analysis and the way the Fragment shares data, it is possible to control access to the ViewModelStore by passing it in. So we implement the ViewModelStoreOwner interface in the Application. Just pass in the Application when you create the ViewModel. Or through custom ViewModelProvider. Factory to do this, make different Activity to obtain the ViewModel returns the same object.

3. Why can’t ViewModel hold objects such as Views or activities? A: Because the Activity/Fragment lifecycle is different from the ViewModel. If you hold a View or an Activity, the Activity can actually be in any lifecycle at this point. The following is a comparison of the Activity and ViewModel lifecycle as the screen rotates.

4. Under what circumstances does the ViewModel save its state? A: The state is saved in the case of ConfigurationChange. If the device is killed due to system reasons, it is not saved. This because of the difference between the onRetainNonConfigurationInstance and onSaveInstanceState mechanism. If a business scenario requires a ViewModel to save state in such a scenario, you can check out SavedStateHandle(ViewModel-SavedState library).

Four,

By tracking the ViewModel creation code, Raises the ViewModel in the process of creating the classes (ViewModelProvider, ViewModelProvider. Factory, ViewModelStore ViewModelStoreOwner) USES, and their way of implement state preserve. We know from the implementation that the ViewModel does not save the state of the page when it is killed by the system. The viewModel-SavedState library is required to implement this functionality.