introduce
ViewModel is one of the components of the ACC framework that addresses data persistence and sharing, and also separates the behavior associated with the data from the UI.
preface
Some understanding of Lifecycle and LiveData may be required for the use and rationale of the ViewModel, otherwise understanding of something may be compromised. The following are available reference materials.
- LifeCycle
- LiveData
The body of the
case
public class MyData extends LiveData<String> {
private static final String TAG = "T-MyData";
public MyData() {setValue("hi");
Log.d(TAG, "create new liveData ");
}
@Override
protected void onActive() {
super.onActive();
Log.d(TAG, "onActive ");
}
@Override
protected void onInactive() {
super.onInactive();
Log.d(TAG, "onInactive ");
}
public void changeValue(String value){
setValue(value); }}Copy the code
public class MainActivity extends AppCompatActivity {
private static final String TAG = "T-MainActivity";
private TabLayout nav;
private Fragment nowFragment;
private Fragment[] fs = new Fragment[]{
new AFragment(),
new BFragment()};
private MViewModel mViewModel;
MyData data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "activity onCreate ");
nav = findViewById(R.id.nav);
nav.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab.getText().equals("A")){
nowFragment = fs[0];
}else{ nowFragment = fs[1]; } getSupportFragmentManager().beginTransaction().replace(R.id.container, nowFragment).commit(); } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) {}}); nav.addTab(nav.newTab().setText("A"));
nav.addTab(nav.newTab().setText("B"));
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
findViewById(R.id.attack).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyData data = mViewModel.getLiveData();
data.changeValue(data.getValue() + "~"); }}); }}Copy the code
public class AFragment extends Fragment {
View mainView;
private TextView text;
private MViewModel mViewModel;
public AFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mainView = inflater.inflate(R.layout.fragment_a, container, false);
text = mainView.findViewById(R.id.A_text);
mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
mViewModel.getLiveData().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
text.setText("A--"+ s); }});returnmainView; }}Copy the code
public class BFragment extends Fragment {
private View mainView;
private TextView text;
private MViewModel mViewModel;
public BFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mainView = inflater.inflate(R.layout.fragment_b, container, false);
text = mainView.findViewById(R.id.B_text);
mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
mViewModel.getLiveData().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
text.setText("B--"+ s); }});returnmainView; }}Copy the code
public class MViewModel extends AndroidViewModel {
private MyData data;
public MViewModel(Application application) {
super(application);
data = new MyData();
}
public MyData getLiveData() {returndata; }}Copy the code
The page is shown below.
A and Bfragment retrieve the LiveData from the ViewModel and listen to the data. There is A button on the Activity. Each click on the button updates the String data with “~” added to it. Behavior: switch back and forth between A and B buttons several times on the single machine, and you can see that the data is the latest in the Fragment (the figure is not stick, lazy). Flip the screen and observe again, and the log is as shown in the figure below
- The Activity and Fragment are rebuilt
- The binding relationship between LiveData and fragments is reconstructed
- ViewModel has not been rebuilt and holds the original data object.
- Data retention sharing
How does ViewModel persist and share data? Here’s how
remind
Because of the version problem, ViewModel is compatible with the SDK of the lower version, so there are two ways to implement the principle. Here is the situation in advance to facilitate the following narrative sequence.
Principle 1 (this SKD is 27)
The entrance
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
Copy the code
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
returnof(activity, null); } public static ViewModelProvider of(@NonNull FragmentActivity activity, @nullable Factory Factory) {// Get the Application that the current program is based on.if(factory == null) {// Get AndroidViewModelFactory, Singleton factory = ViewModelProvider. AndroidViewModelFactory. GetInstance (application); } // ViewModelStores. Of () returns ViewModelStorereturn new ViewModelProvider(ViewModelStores.of(activity), factory);
}
Copy the code
Just notice here that of() returns the ViewModelProvider, which holds the ViewModelStore information and the AndroidViewModelFactory. And the ViewModelStore actually plans how it’s going to be stored
Viewmodelprovider. of() -viewModelProvider () -- ViewModelStores. Of () public static ViewModelStore of(@nonnull FragmentActivity Activity) {// Under the current SDK, run here to return, evidence in the next code reference diagramif (activity instanceof ViewModelStoreOwner) {
return ((ViewModelStoreOwner) activity).getViewModelStore();
}
return holderFragmentFor(activity).getViewModelStore();
}
Copy the code
public class FragmentActivity extends BaseFragmentActivityApi16 implements
ViewModelStoreOwner,
Copy the code
Since the FragmentActivity implements a ViewModelStoreOwner, it can be obtained as follows
Current location FragmentActivity.getViewModelStore () public ViewModelStoregetViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call."); } // Create an empty oneif (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
Copy the code
As you can see, the FragmentActivity itself holds the ViewModelStore
The ViewModelProvider is constructed, and then the specific ViewModel is obtained
ViewModelProvider 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 viewModel viewModel = mViewModelStore.get(key); // Obtain, returnif (modelClass.isInstance(viewModel)) {
return (T) viewModel;
} else {
if(viewModel ! ViewModel = mFactory.create(modelClass); // Create viewModel (AndroidViewModelFactory); // Bind the ViewModel to the class name and save mViewModelStore.put(key, ViewModel);return (T) viewModel;
}
Copy the code
The code above is access to specific ViewModel which AndroidViewModelFactory. The create () code is just through reflection to create a ViewModel instance and catch the exception. A summary:
- ViewModelProvider holds ViewModelStore information
- VIewModelStore plans its own extraction and holds ViewModel information
- Using the class as the key to obtain the concrete ViewModel to achieve sharing
As we know, an Activity is rebuilt if it is not configured during screen rotation, and its data should be released when the Activity is destroyed. So how did the ViewModel survive?
thinking
By the time the source code sees this, the positive clues are virtually untraceable, because in the large Activity architecture, it is difficult to quickly find the origin of the event. Now, how do you find clues to the persistence of the ViewModel? In the current condition, FragmentActivty is directly hold ViewModelStore information, so in the configuration change needs to rebuild, must do some processing of ViewModelStore, So chose to track FragmentActity mViewModelStore.
Sure enough, the following location was located
FragmentActivity
public final Object onRetainNonConfigurationInstance() {... NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = mViewModelStore; nci.fragments = fragments;return nci;
}
Copy the code
When configuration changes need to rebuild the page, the system will be to save the site in order to restore, in this function, ViewModelStore was one of the state is saved in the NonConfigurationInstances. By analogy, where there is a save, there is a take out, continue to track, locate the following position
FragmentActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
.....
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if(nc ! = null) { mViewModelStore = nc.viewModelStore; }... }Copy the code
As you can see, during the life cycle onCreate, the backup ViewModelStore is taken out. So, when you get the ViewModel again via viewModelProvider.get () after the rebuild, it will get the ViewModelStore directly and pull out the ViewModel, Viewmodels will no longer be rebuilt from AndroidVIewModelFactory.
Here’s a summary:
- More change in the configuration needs to rebuild pages, ViewModelStore will be under the custody of NonConfigurationInstances before reconstruction, and remove the recovery after reconstruction.
Above, is the ViewModel in the high SDK data sharing and persistence principle.
Next, it’s for the lower version.
Principle 2 (instance SDK is 25)
As mentioned earlier, ViewModelStore plans how it will be stored, and the difference is that the parent classes of the activities in the earlier SDK versions are not ViewModelStoreOwner. Look back at the code viewModelStore.of ().
public static ViewModelStore of(@NonNull FragmentActivity activity) {
if (activity instanceof ViewModelStoreOwner) {
return((ViewModelStoreOwner) activity).getViewModelStore(); } // Under the current SDK, run to this returnreturn holderFragmentFor(activity).getViewModelStore();
}
Copy the code
The above code gets the ViewModelStore from the HolderFragment
Private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager(); public static HolderFragment holderFragmentFor(FragmentActivity activity) {returnsHolderFragmentManager.holderFragmentFor(activity); } the current location HolderFragement. HolderFragmentManager HolderFragment holderFragmentFor (FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(FM); // Find the returnif(holder ! = null) {returnholder; } / / by key (activity) remove HolderFragment holder = mNotCommittedActivityHolders. Get (activity); // Get to returnif(holder ! = null) {return holder;
}
if(! mActivityCallbacksIsAdded) { mActivityCallbacksIsAdded =true; activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); } // Insert HolderFragment holder = createHolderFragment(FM); / / bind HolderFragment and key mNotCommittedActivityHolders. Put (activity, holder);return holder;
}
Copy the code
The HolderFragment static object HolderFragmentManager holds the corresponding management of the HolderFragment object and its key. Here because the HolderFragment is being injected, we need to see the initial work.
HolderFragment private ViewModelStore mViewModelStore = new ViewModelStore(); publicHolderFragment() {
setRetainInstance(true);
}
Copy the code
The new HolderFragment is created holding the ViewMolderStore, which was obtained from ViewModelStores. Of ().
In principle 1, objects that have a lifetime will hold the ViewModelStore themselves, while in Principle 2, objects that have a lifetime will hold the ViewModelStore indirectly by injecting HolderFrament. Other processes are consistent.
Now, there is only one question left, how to keep the ViewModelStore that is held by the introduction persistent?
Note that when initializing the HolderFragment, mRetainInstance is set as follows
/**
* Control whether a fragment instance is retained across Activity
* re-creation (such as from a configuration change). This can only
* be used with fragments not in the back stack. If set, the fragment
* lifecycle will be slightly different when an activity is recreated
*/
public void setRetainInstance(boolean retain) {
mRetainInstance = retain;
}
Copy the code
Control whether an instance of the fragment is present during Activity destruction-rebuild. This parameter is valid only when the fragment is not in the back stack. When mRetainInstance is set to trues, the lifecycle behavior is slightly different than when rebuilding.
In short, the HolderFragment is not destroyed and can be retrieved when the corresponding HolderFragment is retrieved again using the key.
As to why the HolderFragment is not destroyed, it is necessary to know how the FragmentManager manages fragments, which goes beyond that.
conclusion
Through the above analysis, it is explained how the data of ViewModel is shared and persisted. The key points are as follows:
- ViewModelProvider holds ViewModelStore and Factory, and is mainly used to obtain the corresponding ViewModelStore
- The ViewModelStore stores the mapping between the key-value situation class and the ViewModel, and realizes data sharing. The Factory is responsible for creating the ViewModel when needed
- ViewModelStore is held by an Activity or Fragment, or indirectly by injecting a HolderFragment
- Activity destroyed and rebuilt for configuration reasons, ViewModelStore NonConfigurationInstances preserved or preserved by HolderFragment, restore this demand from save the place again.
Simple schematic diagram
details
HolderFragment public void onCreate(@nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState); sHolderFragmentManager.holderFragmentCreated(this); } the current location HolderFragment. HolderFragmentManager void holderFragmentCreated (fragments HolderFragment) {/ / get fragments as relying on the parent Fragment parentFragment = holderFragment.getParentFragment();if(parentFragment ! = null) {/ / release the father fragment mNotCommittedFragmentHolders. Remove (parentFragment); parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks( mParentDestroyedCallback); }else{/ / release Activity mNotCommittedActivityHolders. Remove (holderFragment, getActivity ()); }}Copy the code
In the Activity destruction-rebuild state, although the ViewModelStore is saved with the HolderFragment, the Activity or Fragment bound to the HolderFragment is no longer the object of the time, thus causing a memory leak. Therefore, solve this problem in the HolderFragment lifecycle onCreate().
Note that the chain between the HolderFragment and the Activity or Fragment no longer exists, and then retrieve the corresponding HolderFragment which is, HolderFragmentFor () ->findHolderFragment(), as shown in the figure below