I have been working with MVP architecture for a long time. Although the coupling relationship between M layer and V layer has been well solved, the problems that are difficult to reuse and single test with a large number of interfaces have been lingering in my mind for a long time. Therefore, I turned my attention to MVVM.
First of all, this article is not an introduction to the use of ViewModel. It is more of a basic principle. If you are not aware of its use, you are not recommended to read it directly
The biggest difference between MVVM and MVP is that it replaces the P layer with a ViewModel, which is the VM in this case. In a word, it is characterized by the holding and maintenance of data state. In other words, it consolidates the logical operations and processing of data in the ORIGINAL P layer into the VM, while the remaining operations in the V layer are recommended to use Databinding, resulting in the most concise and efficient MVVM architecture. Speaking of which, recommend an old text DataBinding, again will not learn you chop me (department brothers chop me series?) .
Back to the characteristics of the VM – the holding and maintenance of data state. Why do we need to do this? In fact, the purpose is to solve two common development problems.
- Activity configuration changes retain data when recreated (such as screen rotation)
- UI components (activities and fragments, fragments and fragments) share data.
For the first case, the data can only be saved through onSaveInstanceState. After the activity is rebuilt, it can be retrieved from the bundle of onCreate or onRestoreInstanceState. However, if the amount of data is large, Serialization and deserialization of data incur performance overhead.
For the second, if no VM is used, each UI component must hold a reference to the shared data, which will cause two troubles. First, if shared data is added, each UI component needs to declare and initialize the newly added shared data again. Second, a UI component cannot directly notify other UI components of changes to shared data, so the observer mode needs to be implemented manually. VM and LiveData can easily achieve this.
LiveData is the driver of data changes, with which the VM can write very concise MVVM code.
Let’s take a look at how VM implements the above requirements, but in fact the core can be translated into the following two questions.
The problem
- How does VM solve the problem of sharing data between activities and fragments?
- How does the VM retain data when an Activity is rotated? Don’t you also go onDestroy?
What is a ViewModel?
Before we answer that question, let’s take a look at what VM really is.
Android Architect Component (AAC) ViewModel Version 1.1.1
public abstract class ViewModel {
protected void onCleared() {}}Copy the code
It’s an abstract class and it doesn’t even have abstract methods. It’s incredibly simple. The onCleared method, which provides an opportunity to release resources in the VM, is called during the onDestroy life cycle of the Activity/Fragment. The class library internally provides an implementation class AndroidViewModel.
public class AndroidViewModel extends ViewModel {
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
public <T extends Application> T getApplication() {
return(T) mApplication; }}Copy the code
AndroidViewModel holds references to applications within it, so you can usually do some full life cycle work. Why not hold an Activity? This has to do with the lifecycle of the ViewModel, which we’ll explain later.
Create a ViewModel
Let’s look at the creation of the VM. This is how we normally create a VM.
val viewModel = ViewModelProviders.of(this).get(AndroidViewModel::class.java)
Copy the code
Here this can be a FragmentActivity or Fragment, both of which are controls in the support-V4 package for backward compatibility. Let’s take the FragmentActivity example and see what the of method does.
1.1 ViewModelProviders # # #
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if(factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } // The factory mode is used here, the vm creation is done by the factory, the default is AndroidViewModelFactoryreturn new ViewModelProvider(ViewModelStores.of(activity), factory);
}
Copy the code
The return values of these two methods are ViewModelProvider. ViewModelProviders are utility classes that operate on ViewModelProvider, and inside are static methods that obtain ViewModelProvider. The real business of the VM is in the ViewModelProvider.
The same is true for ViewModelStores and ViewModelStores. If you want to retain VM data, you must have a storage unit. The ViewModelStore is the storage unit. Since there may be multiple VMS in an Activity, you need a Map to maintain the relational table, with the key being the name of the VM and the value being the VM object.
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if(oldViewModel ! = null) { oldViewModel.onCleared(); } } final ViewModel get(String key) {return mMap.get(key);
}
public final void clear() {
for(ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); }}Copy the code
ViewModelStores is only responsible for providing tooling methods to create a ViewModelStore.
# # # 1.2
public class ViewModelStores {
private ViewModelStores() {
}
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if (activity instanceof ViewModelStoreOwner) {
return ((ViewModelStoreOwner) activity).getViewModelStore();
}
returnholderFragmentFor(activity).getViewModelStore(); }... }Copy the code
What the hell is ViewModelStoreOwner? It abstracts an interface to get the ViewModelStore. Common implementation classes are fragments/FragmentActivity, this also is easy to understand, because to keep data in the configuration change, first of all need to store the data.
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
Copy the code
I feel like I need a class diagram, or are you trying to hack me to death?
In fact, the classes mentioned so far cover almost all classes in the ViewModel library.
Let’s go back to the code that created the ViewModelProvider in 1.1.
return new ViewModelProvider(ViewModelStores.of(activity), factory);
Copy the code
We pass in the VM storage unit in the activity and the VM creation factory (AndroidViewModelFactory) to get a ViewModelProvider object, and finally get the VM through its GET method.
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);
}
Copy the code
Get the VM object prefixed by Default_Key with the VM’s full class name key.
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); ViewModelStore = ViewModelStore = ViewModelStoreif (modelClass.isInstance(viewModel)) {
return(T) viewModel; }... // If it does not exist, create a new VM and store it in ViewModelStore viewModel = mFactory.create(modelClass); mViewModelStore.put(key, viewModel);return (T) viewModel;
}
Copy the code
The default mFactory is AndroidViewModelFactory, so let’s see how it creates the VM.
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
...
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if(AndroidViewModel. Class. IsAssignableFrom (modelClass)) {try {/ / create AndroidViewModel object through reflectionreturnmodelClass.getConstructor(Application.class).newInstance(mApplication); } catch (NoSuchMethodException e) { ... }}returnsuper.create(modelClass); }}Copy the code
Now that we are done with VM creation, we can summarize:
- ViewModelStore is VM unit of data storage structure for Map, fragments/FragmentActivity holds its reference.
- The ViewModelProvider creates a VM using the get method. Before creating a VM, it checks whether the VM exists in the ViewModelStore. If not, it creates a VM through reflection.
Data sharing between UI components
Let’s go back to our first question: how activities and fragments share data. The easiest way to share data is for everyone to read from the same data source. Let’s take a look at where the data source ViewModelStore is created.
As shown in the above code snippet 1.2, there are two paths to create.
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if(Activity Instanceof ViewModelStoreOwner) {①return ((ViewModelStoreOwner) activity).getViewModelStore();
}
returnholderFragmentFor(activity).getViewModelStore(); (2)}Copy the code
① If FragmentActivity is of type ViewModelStoreOwner, it is created using the getViewModelStore method of the activity.
Otherwise, create it with a HolderFragment.
Start with 1.
### FragmentActivity
private ViewModelStore mViewModelStore;
public ViewModelStore getViewModelStore() {...if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
Copy the code
Simple is ck, since is a member variable that each call will return the same VM, ViewModelProviders. Just make sure call of (activity). The get (AndroidViewModel: : class. Java) represents the activity of the same object.
Add an invisible HolderFragment to the current Activity and record the data source in the fragment.
### HolderFragment extends Fragment implements ViewModelStoreOwner
private ViewModelStore mViewModelStore = new ViewModelStore();
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
returnsHolderFragmentManager.holderFragmentFor(activity); } HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(FM); HolderFragment holder = findHolderFragment(FM);if(holder ! = null) {returnholder; }... // Create a holder = createHolderFragment(FM); .returnholder; } private static HolderFragment createHolderFragment(FragmentManager FragmentManager) {// Create a HolderFragment, tag it, Add to the current screen. HolderFragment holder = new HolderFragment(); fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();return holder;
}
Copy the code
It is not visible because the HolderFragment does not duplicate onCreateView. The reason for this was that earlier versions wanted to use the Fragment’s setRetainInstance()API to keep the Fragment invisible when the Activity’s configuration changed, with only onDetach and onAttach in the lifecycle. Since it has not been rebuilt, the data it holds will naturally be retained, and any other active exit cases that go to onDestroy will clear the data.
### HolderFragment
public HolderFragment() {
setRetainInstance(true); } // Clear data source when destroyed public voidonDestroy() {
super.onDestroy();
mViewModelStore.clear();
}
Copy the code
So which path does it take in practice? FragmentActivity implements the ViewModelStoreOwner interface. ② Did you go to the wrong set? At this point I felt my intelligence had been insulted.
Appcompat-v7 before 27.1.0 did not implement the ViewModelStoreOwner interface for FragmentActivity, which was implemented in HolderFragment. Google most likely decided that the HolderFragment operation was a bit too blatant and let later versions of FragmentActivity support VM storage directly. Adding a HolderFragment also has maintenance costs, and the upper layer can get it through FragmentManager and do other things with it.
If you pass FragmentActivity when creating a VM, how about passing FragmentActivity when creating a VM? In fact, much like FragmentActivity, there is a mViewModelStore member variable in the Fragment. Note that this is crucial. This means that if we create two VMS using the following code, the data cannot be shared.
### MainActivityoverride fun onCreate(savedInstanceState: Bundle?) {/ / transfer activity val vm = ViewModelProviders. Of (this). The get (AndroidViewModel: : class. Java)}### TestFragmentoverride fun onCreate(savedInstanceState: Bundle?) {super. OnCreate (savedInstanceState) / / transfer fragments val vm = ViewModelProviders. Of (this). The get (AndroidViewModel: : class. Java) }Copy the code
The correct way to write this is that both pass in the same object in the of method
### TestFragment.onCreate(savedInstanceState: Bundle?)Val vm = Activity? . Run {/ / run function This here refers to the activity ViewModelProviders. Of (this) [AndroidViewModel: : class. Java]}Copy the code
In the same way, you can obtain VMS from activities or parentFragments (if any) at the same level. If there are nested relationships, the VM can be obtained using the parentFragment object.
Data is retained when the Activity configuration changes
Holderfragments retain data using the Fragment’s setRetainInstance()API.
Google optimizes this by directly allowing FragmentActivity to support data retention in the new version, and the official illustration nicely illustrates the VM life cycle when the Activity is destroyed and rebuilt due to configuration changes.
Let’s examine how this is done, starting with the destruction logic of onDestroy.
### FragmentActivity
protected void onDestroy() { super.onDestroy(); .if(mViewModelStore ! = null && ! mRetaining) { mViewModelStore.clear(); }... }Copy the code
When mRetaining is true VM data is retained, when is it true?
### FragmentActivity
public final Object onRetainNonConfigurationInstance() {
if(mStopped) {// Inside the method mRetaining is set totrue
doReallyStop(true); }... NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; // Save VM data nci.viewModelStore = mViewModelStore; nci.fragments = fragments;returnnci; // Data returned will be recorded}Copy the code
OnRetainNonConfigurationInstance methods are retainNonConfigurationInstances method calls, and it will be ActivityThread performDestroyActivity method calls, It is executed before the onDestroy life cycle.
### ActivityThread
performDestroyActivity(...boolean getNonConfigInstance,...) {
...
if(getNonConfigInstance) {/ / if the configuration changes recorded r.l astNonConfigurationInstances = state Richard armitage ctivity. RetainNonConfigurationInstances (); }}Copy the code
This data is encapsulated to NonConfigurationInstances a VM object.
When is it restored? The answer is attach time of the Activity.
### Activityfinal void attach(Context context, ... NonConfigurationInstances lastNonConfigurationInstances,...) {... mLastNonConfigurationInstances = lastNonConfigurationInstances; . }Copy the code
The VM data source mViewModelStore is reassigned at onCreate.
### FragmentActivityprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); / / restore data NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance ();if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
...
}
Copy the code
Finally, we sort out the overall flow chart.
As you can see from the flow above, the VM is retained when the Activity is rebuilt due to configuration changes. From a lifecycle perspective, the ViewModel life cycle may be longer than the Activity life cycle.
This shows that we must be careful when using the ViewModel, do not let it reference the Activity or View, otherwise it may cause memory leaks.
Well, that’s the end of the ViewModel analysis, and hopefully you’ve gained some insight into the MVVM pattern while understanding how it works. The ViewModel is more like a data container, and is easier to understand in conjunction with LiveData, which will be shared in subsequent chapters.