preface

Jetpack AAC series of articles:

What about Jetpack Lifecycle? Also the liver? It’s time for Jetpack LiveData to take a peek at the Jetpack ViewModel

While Lifecycle and LiveData were analyzed in the first two articles, this article will focus on the ViewModel and its relationship to them. Through this article, you will learn:

1. Why do WE need ViewModel? 2, the ViewModel way of using 3, ViewModel principle dig three feet 4, Lifecycle/LiveData ViewModel

1. Why do WE need ViewModel?

Configuration item changed to data recovery

OnCreate (xx) : onCreate(xx) : onCreate(xx) : onCreate(xx) OnResume (), the Activity is now a new instance, so the ViewTree associated with the previous Activity will be rebuilt. In addition to switching between horizontal and vertical screens, other configuration changes also rebuild the Activity.

The question is: how to recover the data from the previous ViewTree binding?

Traditional data recovery methods

Android onSaveInstanceState/onRestoreInstanceState originally to this understanding has been analyzed, the simple mention:

1. Save ViewTree data in onSaveInstanceState. 2. Restore ViewTree data in onRestoreInstanceState.

These two methods allow data to be recovered when configuration items change. But there are drawbacks to this approach:

OnSaveInstanceState uses bundles to store data for easy cross-process transfer, so its storage limit is limited by Binder(1M) and cannot be used to recover large data, such as bitmaps. 2. Complex classes need to implement the Parcelable/Serializable interface. 3. OnSaveInstanceState is called after onStop and is called frequently.

ViewModel can realize data recovery function, also avoids the above problems.

Unified management of UI data

In the past, when managing UI data, we usually define a Model and then define a Manager for unified management. With the help of ViewMode+LiveData, we can manage data more gracefully.

To sum up, ViewModel can perform data recovery and unified MANAGEMENT of UI data.

2, the use of ViewModel

Data restoration for horizontal and vertical switching

The ViewModel looks great, it has code and it has truth, but let’s use it as an example. Define the ViewModel:

public class MoneyViewModel extends ViewModel { private int money; Private String name = "official ViewModel"; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; }}Copy the code

MoneyViewModel inherits from ViewModel. For comparison, define a pure class:

public class MyViewModel { private int money; Private String name = "ViewModel"; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; }}Copy the code

The only difference is whether they inherit from the ViewModel.

XML defines two TextViews and a Button.

When you click modify text, modify the above two TextViews. The code in the Activity is as follows:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_model); MoneyViewModel = new ViewModelProvider(this).get(moneyViewModel.class); myViewModel = new MyViewModel(); TextView tvVM = findViewById(R.id.tv_vm); tvVM.setText(moneyViewModel.getName()); TextView tvMyVM = findViewById(R.id.tv_my_vm); tvMyVM.setText(myViewModel.getName()); Button btnChange = findViewById(R.id.btn_change); BtnChange. SetOnClickListener ((v) - > {moneyViewModel. Elegantly-named setName (" official ViewModel change "); tvMyVM.setText(moneyViewModel.getName()); Myviewmodel.setname (" myViewModel changes "); tvVM.setText(myViewModel.getName()); }); }Copy the code

After clicking Button, modify my ViewModel and official ViewModel, and the UI refreshes. Next, rotate the screen to landscape to view the UI display.

As you can see, when portrait switches to landscape, the official ViewModel changes, but my ViewModel doesn’t change. Obviously, the ViewModel can recover data after switching between horizontal and vertical screens.

3, ViewModel principle digging three feet

ViewModelStore access

MoneyViewModel and myViewModel are also member variables of ViewModelActivity. The ViewModelActivity is rebuilt (re-new the ViewModelActivity instance). Theoretically, member variables are also reconstructed, so why does moneyViewModel keep the data? That’s the ViewModel principle we’re going to explore. Start with ViewModel creation:

moneyViewModel = new ViewModelProvider(this).get(MoneyViewModel.class);
Copy the code

ViewModelProvider is what the name suggests: the provider of a ViewModel. The constructor takes ViewModelStoreOwner, the only method for this interface:

ViewModelStore getViewModelStore();
Copy the code

We see this passed in, which means our ViewModelActivity implements this interface.

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
Copy the code

MFactory and mViewModelStore are ViewModelProvider member variables. MFactory to create ViewModel factory method, mViewModelStore for memory, the ViewModel. It is through the ViewModelStoreOwner getViewModelStore ().

ViewModelActivity inherits from AppCompatActivity, which in turn inherits from ComponentActivity, and ComponentActivity implements ViewModelStoreOwner interface as follows:

#ComponentActivity.java public ViewModelStore getViewModelStore() { ... ensureViewModelStore(); return mViewModelStore; } void ensureViewModelStore() {if (mViewModelStore == null) {// null, From mLastNonConfigurationInstances NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc ! MViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) {mViewModelStore = new ViewModelStore(); }}}Copy the code

It can be seen that ViewModelProvider. MViewModelStore from ComponentActivity. MViewModelStore, And ComponentActivity. MViewModelStore assignment has two places:

1, from the Activity. MLastNonConfigurationInstances get. 2, create a new, direct new ViewModelStore.

To acquire the ViewModel

ViewModelProvider.get(MoneyViewModel.class)
Copy the code
#ViewModelProvider.java 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); } public <T extends ViewModel> T get(@NonNull String key, @nonnull Class<T> modelClass) {// Get ViewModel from ViewModelStore ViewModel = mViewModelStore. if (modelClass.isInstance(viewModel)) { if (mFactory instanceof ViewModelProvider.OnRequeryFactory) { ((ViewModelProvider.OnRequeryFactory) mFactory).onRequery(viewModel); } // return (T) viewModel; } else { ... } the if (mFactory instanceof ViewModelProvider. KeyedFactory) {/ / according to the factory to create the ViewModel ViewModel = ((ViewModelProvider.KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); } mViewModelStore.put(key, ViewModel); return (T) viewModel; }Copy the code

Similar to the ViewModelStore source, viewModels come from two places:

1. Fetch it from ViewModelStore. 2. Construct new instances by reflection.

The newly generated ViewModel will be stored in the ViewModeStore. ViewModeStore has only one member variable:

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

The Map key here is the fully qualified class name + prefix of the custom ViewModel.

The impact of Activity rebuilding on the ViewModel

ViewModelStore is held by the Activity, and ViewModel is held by the ViewModelStore. When the screen switches from portrait to landscape, the Activity rebuilds, but the ViewModel we get is unchanged. We have reason to believe that the ViewModelStore hasn’t changed, and if we look at the ViewModelStore assignment, ViewModelStore at this time is likely to be obtained from the NonConfigurationInstances. Then analysis NonConfigurationInstances context.

It is important to note that NonConfigurationInstances in the Activity. Java and ComponentActivity has defined in Java.

#Activity.java public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances ! = null ? mLastNonConfigurationInstances.activity : null; }Copy the code

The key view mLastNonConfigurationInstances assignment, it call stack is as follows:

PerformLaunchActivity () is called both when a new Activity is created and when a rebuild Activity is created, but when a new Activity is called, The final ActivityClientRecord lastNonConfigurationInstances = null.

The focus turns to the ActivityClientRecord, how it is determined.

#ActivityThread.java public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) { ... // Final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); ActivityClientRecord r = mActivities.get(tmp.token); . handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); }Copy the code

While rebuilding the Activity:

1. Look for ActivityClientRecord from mActivities. 2, find lastNonConfigurationInstances through ActivityClientRecord.

Then look at ActivityClientRecord. Where lastNonConfigurationInstances assignment. We know the steps for rebuilding an Activity:

1. Destroy the original Activity first. 2. Create a new Activity instance.

Called when the Activity is destroyed

#ActivityThread.java ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); . if (r ! = null) { activityClass = r.activity.getClass(); . If (getNonConfigInstance) {try {/ / to get from the Activity r.l astNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { ... }}... }... Synchronized (mResourcesManager) {// Remove ActivityClientRecord mactivities. remove(token); }... return r; }Copy the code

Key to view the activity. RetainNonConfigurationInstances () :

# Activity. Java NonConfigurationInstances retainNonConfigurationInstances () {/ / ComponentActivity rewrite the Object the method of the Activity = onRetainNonConfigurationInstance(); . NonConfigurationInstances nci = new NonConfigurationInstances(); //activity = ComponentActivity.NonConfigurationInstances nci.activity = activity; . return nci; }Copy the code

Then see onRetainNonConfigurationInstance method.

#ComponentActivity.java public final Object onRetainNonConfigurationInstance() { ViewModelStore viewModelStore = mViewModelStore; . NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; // Store ViewModeStore nci.viewModelStore = viewModelStore; return nci; }Copy the code

Finally, ViewModeStore. ComponentActivity. NonConfigurationInstances storage viewModelStore, Then ComponentActivity. NonConfigurationInstances stored in the Activity. NonConfigurationInstances. In the Activity.

The real reason the ViewModel can recover

To recap and summarize:

1. When the Activity is created, the ViewModeStore is created, and the ViewModel store stores the ViewModel. 2, when vertical screen switch to landscape, the first Activity destroyed, at this point will be called to performDestroyActivity (), the method in ViewModeStore encapsulation in NonConfigurationInstances, And NonConfigurationInstances finally assigned to ActivityClientRecord. LastNonConfigurationInstances. The ActivityClientRecord instance is stored in the ActivityThread Map (stored when the Activity starts). After the Activity is destroyed, when rebuilding the Activity, find the ActivityClientRecord associated with the Activity that was destroyed by the ActivityThread Map. Take out lastNonConfigurationInstances assigned to the Activity. MLastNonConfigurationInstances. New ViewModelProvider(this).get(moneyViewModel.class) == null; So from the Activity. MLastNonConfigurationInstances get, at this time will be able to get the last ViewModeStore, then get the ViewModel.

The above steps are the process of ViewMode data recovery.

The core is this: ViewModels are ultimately stored in ActivityThreads, and activityThreads always exist (consistent with the process life cycle) and have nothing to do with the Activity life cycle.

This is why it is often said that you cannot hold a reference to an Activity in a ViewModel, because a ViewModel has a longer life cycle than an Activity.

At this point, you might be wondering: How long is the ViewModel life cycle? Lifecycle listens for Activity Lifecycle when an Activity is in the “ON_DESTROY” state.

#ComponentActivity.java if (event == Lifecycle.Event.ON_DESTROY) { if (! IsChangingConfigurations () {// If the configuration item has not changed, the Activity is considered to destroy ViewModelStore. GetViewModelStore ().clear(); }}Copy the code

Finally remove the ViewModel from the ViewModeStore Map. When we override the viewModel.oncleared () method, it will be called when the ViewModel is cleared for some resource release.

Therefore, the ViewModel lifecycle is as follows:

The ViewModel is destroyed as the Activity is destroyed when the configuration item is not changed. 2. The ViewModel life cycle is longer than the Activity’s when a configuration item changes and the Activity rebuilds.

4, Lifecycle/LiveData ViewModel

The ViewModel advantage

Java itself is very simple, and the system has done a lot of work to restore the ViewModel. advantage

1, ViewModel does not limit type, does not limit size. 2, there is no onSaveInstanceState save/restore data defects. 3. ViewModel and LiveData can not only separate UI and data, but also drive UI through data. 4. The ViewModel is obtained through the Activity. As long as you get the Activity instance, you have the opportunity to get the ViewModel, so it can be used as multiple fragments for data sharing.

Lifecycle is associated with LiveData

With the help of Lifecycle, LiveData can monitor the Lifecycle, distinguish active and inactive states, and realize a life-cycle aware data.

The ViewModel is associated with Lifecycle

It’s not connected. It’s not used in conjunction.

ViewModel is associated with LiveData

The ViewModel separates the UI from the data, and if you want to drive the UI from the data, you need to work better with LiveData. The following is a simple example:

public class LiveDataViewModel extends ViewModel {
    private MutableLiveData<String> mutableLiveData;

    public MutableLiveData<String> getLiveData() {
        if (mutableLiveData == null) {
            mutableLiveData = new MutableLiveData<>();
        }
        return mutableLiveData;
    }
}
Copy the code

More examples are available on Github if you’re interested.

In the analysis of LiveData and ViewModel, both are not mentioned together at the beginning, so that the functional boundaries of the two can be clearly recognized. Because of different application scenarios, both can be used separately, and the combination of the two is more convenient for scenarios sensitive to configuration item changes.

The next article will analyze viewModels and thoroughly clarify why viewModels can store data and where they are used.

This article is based on: implementation ‘androidx. Appcompat: appcompat: 1.4.1’ & Android 10.0

ViewModel demo & tools

If you like, please like, pay attention to, collect, your encouragement is my motivation to move forward

Continue to update, with me step by step system, in-depth study of Android

4, View Measure/Layout/Draw 5, Android events distribution of full service 6, Android invalidate postInvalidate/requestLayout thoroughly clarify 7, how do you determine the Android Window size/onMeasure () to be executed multiple times Android event driver Handler-message-Looper 9, Android keyboard in one move 10, Android coordinates completely clear 11, Android Activity/Window/View background Android IPC series 14, Android Storage series 15, Java concurrent series no longer confusion 16, Java thread pool series 17, Android Jetpack Android Jetpack is easy to learn and easy to understand