The whole process of this article: first to know what is ViewModel, and then demonstrate an example, to see how ViewModel is used, and then ask why it is so, and finally read the source code to explain why!

1. What is ViewModel

Definition 1.1.

The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation. In the corresponding scope, integrity only produces the corresponding unique instance, ensuring communication between UI components.

ViewModel is typically used in conjunction with LiveData and DataBinding

Characteristics of 1.2.

And by definition we can say that

  • The ViewModel is not destroyed as the Activity’s screen rotates;
  • In the corresponding scope, integrity only produces the corresponding unique instance, ensuring communication between UI components

A little bit about the relationship between ViewModel and onSaveInstanceState

  • For simple data, an Activity can use the onSaveInstanceState() method to recover its data from the bundle in onCreate(), but this method is only suitable for small amounts of data that can be serialized and deserialized, not potentially large amounts of data such as user lists or bitmaps.

  • The ViewModel stores large amounts of data without serialization or deserialization

  • OnSaveInstanceState stores a small amount of data

  • Complement each other, not substitute

  • The process is closed so that onSaveInstanceState data is retained while ViewModel is destroyed

2. Basic use of ViewModel

In this example, the User information is printed, and the User information is updated and printed when the button is clicked

2.1 First look at the UserViewModel file
//UserViewModel.kt

// Customize the User data class
data class User(var userId: String = UUID.randomUUID().toString(), var userName: String)

class UserViewModel : ViewModel() {

    private val userBean = User(userName = Shadow of the Blade)
    // Private user LiveData
    private val _user = MutableLiveData<User>().apply {
        value = userBean
    }
    // Exposed LiveData whose value cannot be changed
    var userLiveData: LiveData<User> = _user

    // Update User information
    fun updateUser(a) {
        // reassign _user
        _user.value = userBean.apply {
            userId = UUID.randomUUID().toString()
            userName = "Update: userName = tyrone"}}}Copy the code
  • Custom User data classes
  • Inherit the ViewModel and initialize the User
  • Declare private User LIveData to update data
  • Exposed LiveData whose value cannot be changed
  • UpdateUser () Method for updating User information
2.2. Take a look at the contents of ViewModelActivity again
class ViewModelActivity : AppCompatActivity() {

    // Initialize UserViewModel through the ViewModelProvider
    private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val button = Button(this)
        setContentView(button)
        
        // Observe the User data and print it
        userViewModel.userLiveData.observe(this, Observer { user ->
            "User = $user".log()
        })

        // Click the button to update the User information
        button.setOnClickListener {
            userViewModel.updateUser()
        }
    }
}
Copy the code
  • Initialize UserViewModel
  • Observe the User data and print the results
  • Update User information when the button is clicked
2.3. Result log
//log User = User(userId= 34c1a1A1A4-967E-439C-91E8-795b8c162997, userId= 34c1a1A1A4-967E-439C-91E8-795b8c162997, User = User(userId= a6d0f09C-9c01-412a-ab4f-44bef700d298, userName= new: userName= Telon)Copy the code
2.4 conclusion:

The above is the simple use of ViewModel, which is combined with LiveData. For the specific use and principle analysis of LiveData, please see this article

Android Jetpack component LiveData basic use and principle analysis

According to the above definition and characteristics of ViewModel, it can be known that ViewModel only produces corresponding unique instances within the corresponding scope to ensure communication between UI components

So let’s verify this, and let me write another example to prove this

3. Verify that the ViewModel is in the corresponding scope and only produces the corresponding unique instance

3.1. ViewModelActivity2 class

Add two fragments through supportFragmentManager in ViewModelActivity2

class ViewModelActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
        supportFragmentManager.beginTransaction()
            .add(R.id.flContainer, FirstFragment())
            .add(R.id.flContainer, SecondFragment())
            .commit()
    }
}
Copy the code
3.2. Two fragments
class FirstFragment : Fragment() {
    private val TAG = javaClass.simpleName

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        val userViewModel = ViewModelProvider(activity as ViewModelStoreOwner)[UserViewModel::class.java]
        "userViewModel = $userViewModel".logWithTag(TAG)
        return super.onCreateView(inflater, container, savedInstanceState)
    }
}
Copy the code
class SecondFragment : Fragment() {
    private val TAG = javaClass.simpleName
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        val userViewModel = ViewModelProvider(activity as ViewModelStoreOwner)[UserViewModel::class.java]
        "userViewModel = $userViewModel".logWithTag(TAG)
        return super.onCreateView(inflater, container, savedInstanceState)
    }
}
Copy the code
  • Instantiate the UserViewModel object in the onCreateView methods of FirstFragment and SecondFragment

  • And the parameters are activity as ViewModelStoreOwner which is actually ViewModelActivity2

  • Print the address value of the UserViewModel object to view the log

3.3. Result log
E/FirstFragment: userViewModel = com.jhb.awesomejetpack.viewmodel.UserViewModel@9940311
E/SecondFragment: userViewModel = com.jhb.awesomejetpack.viewmodel.UserViewModel@9940311
Copy the code

You can see that the UserViewModel in both fragments is the same object.

Both fragments can handle such communication by sharing the ViewModel with their Activity scope

4. Ask questions

  • Why the ViewModel is not destroyed as the Activity rotates on the screen;
  • Why does integrity only produce the corresponding unique instance in the corresponding scope to ensure communication between UI components
  • Where is the onCleared method called

5. Preparation before analyzing source code

5.1ViewModel Lifecycle

5.2. Perceptual knowledge of several categories
  • ViewModelStoreOwner: Is an interface to get a ViewModelStore object

  • ViewModelStore: Stores multiple ViewModels. When the owner of a ViewModelStore changes its configuration or rebuilds, it still has this instance

  • ViewModel: a data management class for activities and fragments, usually used with LiveData

  • ViewModelProvider: Creates an instance of ViewModel and stores the ViewModel in the given ViewModelStoreOwner

6. Source code analysis

Look again at the code in the first example above

class ViewModelActivity : AppCompatActivity() {

    // Initialize UserViewModel through the ViewModelProvider
    private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val button = Button(this)
        setContentView(button)

        // Observe the User data and print it
        userViewModel.userLiveData.observe(this, Observer { user ->
            "User = $user".log()
        })

        // Click the button to update the User information
        button.setOnClickListener {
            userViewModel.updateUser()
        }
    }
}
Copy the code

Let’s first look at the initialization of the UserViewModel.

private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

Note: The array-like code above is written Kotlin’s way, but is actually the get method of ViewModelProvider

7. Constructor of ViewModelProvider, and get method

7.1ViewModelProvider Constructor

Look at the ViewModelProvider constructor, which passes in the current AppCompatActivity parameter

//ViewModelProvider.java

private final Factory mFactory;
private final ViewModelStore mViewModelStore;

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

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}
Copy the code
  • Get the ViewModelStore object via ViewModelStoreOwner and assign a value to mViewModelStore
  • Assign to mFactory, in this case the object NewInstanceFactory
7.2. Get method of ViewModelProvider
//ViewModelProvider.java

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

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");
    }
  	/ / 1
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
Copy the code

Note 1:

  • The two-parameter GET method is called

  • The first parameter is a concatenation of strings used to retrieve the corresponding ViewModel instance later, ensuring that the same Key is retrieved from the same ViewModel

  • The second argument is the bytecode file object of UserViewModel

Let’s look at the get method for the two arguments

//ViewModelProvider.java
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);/ / 1
		/ / 2
    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.}}/ / 3
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
  	/ / 4
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}
Copy the code

Note 1: Select a ViewModel from ViewModelStore, according to key, ViewModelStore source code below analysis

Note 2: Check whether the retrieved ViewModel instance and the passed ViewModel instance are the same, and return the cached instance directly

Note 3: Create a ViewModel with Factory

Note 4: Store the newly created ViewModel in ViewModelStore for future use, and finally return the newly created ViewModelStore

So let’s see how the ViewModel is created by Factory, okay

As you can see from section 7.1, this instance of Factory is the NewInstanceFactory

7.3. Create method of NewInstanceFactory
/ / ViewModelProvider. AndroidViewModelFactory in Java. Java
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    //noinspection TryWithIdenticalCatches
    try {
        return modelClass.newInstance();
    } catch (InstantiationException e) {
        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}Copy the code

Simple and crude, we create the ViewModel object directly by reflection.

I’m going to extend one here, in the instance UserViewModel

private val userViewModel by lazy { ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[UserViewModel::class.java] }
Copy the code

It can also be instantiated using a constructor of two arguments, the second of which is Factory. And then we’re going to instantiate UserViewModel with AndroidViewModelFactory, so let’s look at the code

AndroidViewModelFactory is a subclass of NewInstanceFactory

/ / ViewModelProvider AndroidViewModelFactory in Java
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.getConstructor(Application.class).newInstance(mApplication);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Cannot create an instance of "+ modelClass, e); }}return super.create(modelClass);
}
Copy the code

If we create UserViewModel had inherited AndroidViewModel go class modelClass. GetConstructor (Application. The class). NewInstance (mApplication); Instantiate, otherwise go to the parent class’s instantiation method, which is the Create method of the NewInstanceFactory

It is recommended to use the AndroidViewModel class for development, which provides an application-level Context.

So what is ViewModelStoreOwner, and how does it actually work

8.ViewModelStoreOwner

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore(a);
}
Copy the code
  • An interface in which a method returns a ViewModelStore object
  • Its implementation classes are ComponentActivity and Fragment in AndroidX

ComponentActivity key code

//ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner.XXX{
   
    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore(a) {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if(nc ! =null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = newViewModelStore(); }}returnmViewModelStore; }}Copy the code
  • A ViewModelStore is created and returned

Take a look at this ViewModelStore class

9.ViewModelStore

9.1. ViewModelStore source

I posted below is the complete code, you read right.

public class ViewModelStore {
		/ / 1
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
		/ / 2
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if(oldViewModel ! =null) { oldViewModel.onCleared(); }}/ / 3
    final ViewModel get(String key) {
        return mMap.get(key);
    }

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

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

Note 1: Declare a Map to store the ViewModel

Note 2: Store the ViewModel, which we used in section 7.2 in the Get method of the ViewModelProvider

Note 3: Fetch the ViewModel, which we used in section 7.2 in the Get method of the ViewModelProvider. Note that the ViewModel is removed from the Map according to the Key, which is the string DEFAULT_KEY + “:” + canonicalName concatenated in comment 1 in section 7.2. This explains the question in section 4 why does ortho only produce the corresponding unique instance in the corresponding scope

Note 4: To clear the stored data, the ViewModel clear method is also called, and the onCleared() method with the ViewModel is eventually called

So when does the ViewModelStore clear method get called?

9.2.ComponentActivity constructor
//ComponentActivity.java
public ComponentActivity(a) {
    Lifecycle lifecycle = getLifecycle();
   	
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
          	/ / 1
            if (event == Lifecycle.Event.ON_DESTROY) {
                if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); }Copy the code

In the Constructor for ComponentActivity, we see that a configuration change (such as a vertical screen switch) calls the ViewModelStore clear method when the Activity’s life cycle is onDestory, and it is not currently. Call the onCleared method of the ViewModel further.

This answers the question posed in section 4 where is the onCleared method called

Finally take a look at the ViewModel source, and its subclass AndroidViewModel

10. The source code of the ViewModel

The ViewModel class is really more like a more formal abstract interface

public abstract class ViewModel {
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared(a) {}@MainThread
    final void clear(a) {
        mCleared = true;
        if(mBagOfTags ! =null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsentcloseWithRuntimeException(value); } } } onCleared(); }}Copy the code

Subclass of ViewModel AndroidViewModel

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /** * Return the application. */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication(a) {
        return(T) mApplication; }}Copy the code

Provides a specification that provides an Application Context

Up to now the entire source process on the look, including the front, we mentioned the source code of several key classes.

So far, we have solved two of the problems we raised in Section 4. One is why the ViewModel does not destroy as the Activity’s screen rotates.

11. Analyze why the ViewModel does not destroy as the Activity’s screen rotates

The first thing we know is that the ViewModel is not destroyed, it’s stored in a ViewModelStore Map, so we have to make sure that the ViewModelStore is not destroyed.

You have to have a prior knowledge

Provides onRetainNonConfigurationInstance method in the Activity, used for handling configuration changes data preservation. Then in recreating the Activity call getLastNonConfigurationInstance get saved data last time.

11.1. OnRetainNonConfigurationInstance method
//ComponentActivity.java
/**
 * 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(a) {
    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
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null) { viewModelStore = nc.viewModelStore; }}if (viewModelStore == null && custom == null) {
        return null;
    }
		/ / 1
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
Copy the code

Take a look at the comments on the method

  • This method does not and cannot be overridden because it is decorated with final
  • To save data when configuration changes, use the ViewModel
  • Note 1: the ViewModel stored in NonConfigurationInstances object

Now look at the getViewModelStore method for ComponentActivity

//ComponentActivity.java
@NonNull
@Override
public ViewModelStore getViewModelStore(a) {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
      	/ / 1
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = newViewModelStore(); }}return mViewModelStore;
}
Copy the code

Note 1: get the NonConfigurationInstances an object, don’t take a ViewModelStore is empty from them, this is the ViewModelStore previously saved

When the Activity reconstruction will go getViewModelStore method, this time is in ViewModelStore NonConfigurationInstances take a cache.

while

12. To summarize

12.1. Why isn’t the ViewModel destroyed as the Activity rotates

Mainly by onRetainNonConfigurationInstance method the ViewModelStore cached NonConfigurationInstances, were taken out getViewModelStore ViewModelStore. See section 11 for details

12.2. Why does ortho only produce the corresponding unique instance in the corresponding scope

The get method in ViewModelStore is evaluated by key, and if the key is the same, then the ViewModel is the same. See section 9.2 for details

12.3. Where is the onCleared method called

When the Activity is actually destroyed, rather than a configuration change, the ViewModelStore clear is called and the ViewModel onCleared is called, as you can see in Section 9.2 (p. 462)

13. Source code address

ViewModelActivity.kt

14. Original address

Android Jetpack component ViewModel basic use and principle analysis

15. Refer to articles

ViewModel

Android official architecture component ViewModel: From past life to trace the origin

series

  • Android Jetpack component Lifecycle basic usage and principle analysis
  • Android Jetpack component LiveData basic use and principle analysis
  • Android Jetpack component ViewModel basic use and principle analysis

I recommend my open source project WanAndroid client

WanAndroidJetpack architecture diagram

  • A pure Android learning project, WanAndroid client.
  • Project useMVVMArchitecture,KotlinVoice writing.
  • Extensive use of Android Jetpack includes but is not limited toLifecycle,LiveData,ViewModel,Databinding,Room,ConstraintLayoutAnd so on, there may be more in the future.
  • usingRetrofitKotlin-Coroutine coroutinesNetwork interaction.
  • Loading picturesGlideMain load image frame.
  • Data storage is mainly usedRoomAnd tencentMMKV.

The integrated application of Kotlin + MVVM + Jetpack + Retrofit + Glide is a good project to learn MMVM architecture.

This project itself is also a specialized learning Android related knowledge APP, welcome to download experience!

Source code address (with download link)

WanAndroidJetpack

An overview of the APP

Click on whatever you likeStarsPlease address Issues if you have any questions.