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 use
MVVM
Architecture,Kotlin
Voice writing. - Extensive use of Android Jetpack includes but is not limited to
Lifecycle
,LiveData
,ViewModel
,Databinding
,Room
,ConstraintLayout
And so on, there may be more in the future. - using
Retrofit
和Kotlin-Coroutine
coroutinesNetwork interaction. - Loading pictures
Glide
Main load image frame. - Data storage is mainly used
Room
And 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.