preface

As an Android developer, if you’re familiar with the MVVM architecture and the Jetpack component, you’ve probably used the ViewModel.

As its name suggests, it is a Google class that facilitates the implementation of the ViewModel layer in the MVVM architecture. It’s where we process the data that the View layer needs, and then notify the View layer for UI updates under certain conditions.

As the official notes:

The ViewModel class stores and manages interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation.

Let’s get to the point of this sentence:

  1. Lifecycle oriented approach: Self-reclaiming occurs at the right time to prevent memory leaks.
  2. Store and manage data related to the interface: YesThe MVVM architectureIn theThe ViewModel layerThe idea of.
  3. Retention of data after configuration changes such as screen rotation: Why design this way? How did you do that?

Now, let’s take a closer look at the ViewModel class with questions in mind.

Method of use

Before reading the source code, let’s briefly review how ViewModel is used.

class MainViewModel(private val repository: MainRepo) : ViewModel() {

    private val _textMld = MutableLiveData<String>()
    val textLd: LiveData<String> = _textMld
    
    fun getTextInfo(a) {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                // Do asynchronous network request work and get textData
                repository.getTextInfo()
            }.apply {
                _textMld.postValue(textData)
            }
        }
    }
}

class MainActivity : AppCompatActivity() {

    fun setVmFactory(a): ViewModelProvider.Factory {
        return object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return MainViewModel(MainRepo()) as T
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        val vm = ViewModelProvider(this, setVmFactory())[MainViewModel::class.java]
        vm.textLd.observe(this, Observer { 
            binding.textTv.text = it
        })
    }
}
Copy the code

The method can be divided into two steps:

  1. Inheriting the ViewModelClass implementsCustom ViewModel, for example, MainViewModel.
  2. throughViewModelProvidertoInstantiate the ViewModel.

The source code

Now, based on the usage described above, let’s take a closer look at the ViewModel source code.

public abstract class ViewModel {...@SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    finalvoid clear() { .... onCleared(); }... }Copy the code

ViewModel is an abstract class that provides the onCleared() method for clearing the ViewModel before it is destroyed.

Next, let’s look at how a ViewModel object is instantiated using the ViewModelProvider.

It’s really two steps:

  1. Instantiate aViewModelProviderObject.
  2. callViewModelProvider.get()Method to get oneViewModelObject.

Let’s look at its constructor first:

public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
) {

	public constructor(
	    owner: ViewModelStoreOwner
	) : this(owner.viewModelStore, defaultFactory(owner))

	public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
	    owner.viewModelStore,
	    factory
	)

......

}
Copy the code

As you can see from the ViewModelProvider constructor, there are two arguments: ViewModelStore and Factory.

Let’s see what these two parameters mean.

public class ViewModelStore {

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

    final void put(String key, ViewModel viewModel) {
	// Do not use the same key, otherwise the VM created later will be replaced by the old VM
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            // Call the ViewModel clear method to indicate that it is no longer used
            vm.clear();
        }
	// Clear all viewModels in the collectionmMap.clear(); }}Copy the code

ViewModelStore: As the name suggests, it is used to store ViewModel objects. It maintains an internal HashMap to store and manage ViewMoel objects.

Take a look at the Factory introduced above.

The setVmFactory() method is used to instantiate the MainViewModel.

public interface Factory {

    public fun <T : ViewModel> create(modelClass: Class<T>): T

}
Copy the code

Next, let’s look at the Get () method in the ViewModelProvider.

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    //1. Fetch the ViewModel from the ViewModelStore according to the key
    var viewModel = store[key]

    //2. Instantiate the viewModel using the factory method
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)? .onRequery(viewModel)return viewModel as T
    }
    
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        factory.create(modelClass)
    }

    //3. Place the instantiated ViewModel in the ViewModelStore
    store.put(key, viewModel)
    return viewModel
}
Copy the code

To recap the get() method:

  1. Based on the parameter passed inClass.canonicalNameAs key, fromViewModelStoreRetrieve the ViewModel from.
  2. Instantiate the ViewModel through the factory method.
  3. Finally, the instantiated ViewModel is put inViewModelStoreAnd return.

OK, if we analyze the source code according to the usage method, we seem to have finished analyzing 😅. Is this the end of this article?

That’s not enough!

Have you ever wondered why it’s called ViewModel? How does it relate to the ViewModel in the MVVM architectural pattern? How does it perceive the life cycle? Why do you want the ViewModel to stay after the screen rotates?

Next, let’s study and think further with questions.

Why is it called a ViewModel

This is actually the original question that Google designed this class for. We know that in the architectural pattern of Android, from the initial MVC to MVP, Google has not specifically designed some classes to support the use of this architectural pattern for us developers, which indirectly leads to the developers have their own design style. Until the emergence of THE MVVM architecture mode, Google designed some new classes for us to support the MVVM architecture mode in order to reduce the difficulty of developers’ architecture and improve the efficiency of development, including the ViewModel class, which is used to handle the work of the ViewModel layer in the MVVM mode. That’s why it’s called the ViewModel.

How does the ViewModel perceive the lifecycle

Viewmodels have a life cycle, as shown in the following figure:

Have you ever wondered how the ViewModel senses the lifecycle?

If you are familiar with Jetpack you will blurt out the answer: Lifecycle. It turns out that Lifecycle is indeed perceived through Lifecycle.

In fact, the official document also introduced:

The ViewModel object exists for a time Lifecycle that is passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel will remain in memory until Lifecycle, which limits its lifetime, is gone forever.

So let’s take a closer look at how the ViewModel takes Lifecycle to feel the Lifecycle.

Remember using the ViewModelProvider to instantiate a ViewModel as described in using methods?

val viewModel = ViewModelProvider(this, setVmFactory())[getViewModelClass()]
Copy the code

The constructors of ViewModelProvider are ViewModelStore and Factory. Back in the calling method, this refers to the current Activity, which also corresponds to the ViewModelStore in the constructor.

Does the Activity correspond to the ViewModelStore? What’s the correspondence?

At this point, I glanced at the ViewModelStore class comment and found a sentence:

Use {@link ViewModelStoreOwner#getViewModelStore()} to retrieve a {@code ViewModelStore} for activities and fragments.

Get the ViewModelStore for your Activity or Fragment using the ViewModelStoreOwner#getViewModelStore() method

public interface ViewModelStoreOwner {
    /** * returns ViewModelStore */
    @NonNull
    ViewModelStore getViewModelStore();
}
Copy the code

To this point, you should think about it, our Activity | fragments implements ViewModelStoreOwner interface, and implements the getViewModelStore () method to get ViewModelStore.

Sure enough, the Activity’s parent ComponentActivity class implements the ViewModelStoreOwner interface and implements the getViewModelStore() interface method.

ComponnetActivity.kt

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner
	...... {

	public ViewModelStore getViewModelStore() {
            // Check if the current mViewModelStore is empty
	    if (mViewModelStore == null) {
		// Retrieve the previously saved non-configured instance data
	        NonConfigurationInstances nc =
	                (NonConfigurationInstances) getLastNonConfigurationInstance();
	        if(nc ! =null) {
                    // Retrieves ViewModelStore from previously saved non-configured instance data
	            mViewModelStore = nc.viewModelStore;
	        }
	        if (mViewModelStore == null) {
                    // If no non-configured instance data has been saved previously, create a ViewModelStoremViewModelStore = new ViewModelStore(); }}returnmViewModelStore; }}Copy the code

To summarize: In the step of obtaining ViewModelStore, the first step is to retrieve whether there is previously saved non-configured instance data. If there is, the ViewModelStore is retrieved. Otherwise, a new ViewModelStore is created if it has not been saved before.

So, the viewModelStore is retrieved in the ViewModelProvider constructor by calling the owner.viewModelStore method, This step is to take the Activity | fragments of getViewModelStore () method to create ViewModelStore, also is what we have just introduced.

Lifecycle doesn’t seem to have anything to do with it when we get here.

Let’s take a closer look at the invocation relationship between viewModelStore and Lifecycle.

public ComponentActivity() {
    ......

    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {

            if (event == Lifecycle.Event.ON_DESTROY) {
                // Check to see if configuration changes have occurred
                if(! isChangingConfigurations()) {// Call the clear() method in ViewModelStore to clear its ViewModelgetViewModelStore().clear(); }}}}); . }Copy the code

Lifecycle is felt through Lifecycle, while the Activity is in a destroyed state, check to see if configuration changes have taken place, and if not, call clear() to clear the ViewModelStore and its saved ViewModels.

Configuration changes will be analyzed later in this article rather than expanded here.

Summarize how the ViewModel is aware of the lifecycle.

First we know that ViewModel is stored in ViewModelStore. When we enter an Activity, a ViewModelStore is automatically created. When we call viewModelProvider.get () in onCreate(), It will store the created ViewModel in the ViewModelStore. Lifecycle is used to inform the Activity’s life, while the Activity is being destroyed, check to see if configuration changes have taken place, and if not, call clear() to clear the ViewModelStore and its saved ViewModels.

Why does ViewModel data persist after the screen is rotated

When we do not set the configChanges property for the Activity, the Activity will rebuild when we rotate the screen, and the ViewModel will keep the data.

How does this work?

As mentioned above, when the Activity is being destroyed, it checks to see if configuration changes have occurred, and if not, the clear() method is called to clear the ViewModelStore and its saved ViewModels.

So if you look at it this way, there must have been a configuration change when we rotated the screen, because it was that configuration change that kept our ViewModel from getting cleaned up and keeping the state data.

When the Activity for the configuration changes (such as: rotating screen) be destroyed and rebuilt, system will immediately call onRetainNonConfigurationInstance () method.

ComponentActivity.java

public final Object onRetainNonConfigurationInstance(a) {
    // Maintain backward compatibility.
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // Get the previously created non-configured instance data
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null) { viewModelStore = nc.viewModelStore; }}if (viewModelStore == null && custom == null) {
        return null;
    }

    // If no non-configured instance data has been created before, create a new one and return it.
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}
Copy the code

Will first through getLastNonConfigurationInstance () method to get the previously created the configuration instance data, but if the previously did not create configuration instance data, create a new, and return.

Activity.java

NonConfigurationInstances mLastNonConfigurationInstances;

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

final void attach(NonConfigurationInstances lastNonConfigurationInstances ...) {... mLastNonConfigurationInstances = lastNonConfigurationInstances; . }static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}
Copy the code

It can be seen that mLastNonConfigurationInstances is the Activity of the attach () method of the assignment, so we need to look up the Activity of startup process.

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {... Activity activity =null; activity.attach(r.lastNonConfigurationInstances ...) ; r.lastNonConfigurationInstances =null; .return activity;
}
Copy the code

That is, the initial non-configured instance is created by the ActivityClientRecord in the ActivityThread. The ActivityClientRecord is not affected when the Activity is rebuilt due to configuration changes. So when the screen rotation Activity reconstruction (configuration changes), first onRetainNonConfigurationInstance () method will be called to return a contains the current ViewModelStore configuration instance objects, Then later via getLastNonConfigurationInstance () method to get to the non configuration instance, so save the ViewModel in its ViewModelStore is cleared.

One more thing about configuration changes.

  • Rotating the screen is actually a configuration change, isChangingConfigurations = true.
  • IsChangingConfigurations = false When jumping to another Activity (the Activity exits normally or the system kills it) the configuration is not changed.

Why design the ViewModel to survive configuration changes

Now we know that the ViewModel is not cleared when the Activity is rebuilt with a configuration change, so its saved data still exists.

Have you ever wondered why?

We know that the ViewModel class handles the work of the ViewModel layer in the MVVM architectural pattern, so the ViewModel retains UI state data. When you rotate the screen, the Activity does a reconstruction, switching our XML layout from Portrait to landscape, but the data displayed in the layout is the same. Therefore, we do not need to go to the Model layer to retrieve the data, directly reuse the data in the ViewModel, thus saving system overhead.

In addition, before the advent of the ViewModel, the onSaveInstanceState() method was overwritten to preserve data when an Activity was destroyed and rebuilt. This method serialized data to disk through the Bundle, which was relatively complex and limited in size. Viewmodels, by contrast, keep data in memory, read and write faster, and have no size limits, so they are a good alternative.

conclusion

This article, we through the use of ViewModel to learn the ViewModel source, and the design of the ViewModel for further thinking, “why is it called ViewModel? How does it relate to the ViewModel in MVVM? How does he perceive the life cycle? Why do you want to keep the ViewModel after the screen rotates?” These several questions for further study, I believe that you now have a further knowledge and understanding of ViewModel.

That’s the end of this article. If you have any questions or different ideas, feel free to comment in the comments section.

In fact, the biggest purpose of sharing articles is to wait for someone to point out my mistakes. If you find any mistakes, please point them out without reservation and consult with an open mind.

In addition, if you think the article is good and helpful to you, please give me a thumbs-up as encouragement, thank you ~.