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:
- Lifecycle oriented approach: Self-reclaiming occurs at the right time to prevent memory leaks.
- Store and manage data related to the interface: Yes
The MVVM architecture
In theThe ViewModel layer
The idea of. - 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:
Inheriting the ViewModel
Class implementsCustom ViewModel
, for example, MainViewModel.- through
ViewModelProvider
toInstantiate 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:
- Instantiate a
ViewModelProvider
Object. - call
ViewModelProvider.get()
Method to get oneViewModel
Object.
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:
- Based on the parameter passed in
Class.canonicalName
As key, fromViewModelStore
Retrieve the ViewModel from. - Instantiate the ViewModel through the factory method.
- Finally, the instantiated ViewModel is put in
ViewModelStore
And 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 {@codeViewModelStore
} 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 ~.