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