The source code versions involved in this article are as follows:

  • Com. Android. Support: appcompat – v7:27.1.1
  • Android. Arch. Lifecycle: extensions: 1.1.1
  • Android. Arch. Lifecycle: the viewmodel: 1.1.1
  • Android. Arch. Lifecycle: livedata: 1.1.1

What is a ViewModel, and how does it work

Viewmodels are used to store and manage UI-related data. Viewmodels have their own life cycle and will be destroyed according to the fragment or activity life cycle. When the configuration changes, the data stored in the ViewModel still exists and is not destroyed. (Such as rotating the screen, which usually causes the activity to rebuild)

ViewModel related class structure

ViewModel: Abstract class. The onCleared method is used to release resources

AndroidViewModel: Inherits ViewModel, no difference, construct can pass an application

ViewModelStore: ViewModel store, internal Map store

ViewModelStoreOwner: An interface, ViewModelStore owner. Fragment and FragmentActivity implement interfaces

Factory: A Factory interface for creating viewModels. Is the internal interface of the ViewModelProvider

NewInstanceFactory: Inherits the Factory and generates a ViewModel by reflecting class.newinstance (). So to use this Factory, the ViewModel needs an empty parameter constructor.

AndroidViewModelFactory: The AndroidViewModel constructor must have only one application parameter. How to create a reflection failure will call the NewInstanceFactory to create it.

ViewModelProvider: is a helper class. The constructor can pass in ViewModelStore and Factory, and generate a ViewModel based on Factory and store it in ViewModelStore.

ViewModel lifecycle

The ViewModel life cycle is associated with the Fragment and activity life cycle. Viewmodel.oncleared () is called only when the fragment activity is destroyed.

The ViewModel is stored in the ViewModelStore. The onCleared method of the ViewModel is called when the ViewModelStore method is clear.

private final HashMap<String, ViewModel> mMap = new HashMap<>();
/** * Clears internal storage and notifies ViewModels that they are no longer used. */
public final void clear(a) {
    for (ViewModel vm : mMap.values()) {
        vm.onCleared();
    }
    mMap.clear();
}
Copy the code

FragmentActivity implementation Fragment implementation Fragment implementation

public ViewModelStore getViewModelStore(a) {
    if (getContext() == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    // A Fragment has only one ViewModelStore
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}

public void onDestroy(a) {
    mCalled = true;
    // Use mStateSaved instead of isStateSaved() since we're past onStop()
    if(mViewModelStore ! =null && !mHost.mFragmentManager.mStateSaved) {
        mViewModelStore.clear(); // mStateSaved is used to determine whether the Fragment has been rebuilt due to configuration changes}}Copy the code

Now look at the implementation of FragmentActivity

    public ViewModelStore getViewModelStore(a) {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

    protected void onDestroy(a) {
        super.onDestroy(); .if(mViewModelStore ! =null && !mRetaining) { 
            // mRetaining is used to determine whether a Fragment is rebuilt due to configuration changesmViewModelStore.clear(); }}Copy the code

The data stored in the ViewModel will not be destroyed when configuration changes. The ViewModel will not be destroyed when configuration changes. The ViewModel will not be destroyed when configuration changes. So just save the mViewModelStore and that’s it. When configuration changes, save data in onSaveInstanceState and restore data in onRestoreInstanceState, But these two methods to store data can only be placed on the Bundle, only small amounts of data, will throw TransactionTooLargeException anomalies. So not appropriate, see source discovery is to use the Activity of onRetainNonConfigurationInstance () and getLastNonConfigurationInstance ().

   // FragmentActivity key code
	public final Object onRetainNonConfigurationInstance(a) {
        Object custom = onRetainCustomNonConfigurationInstance();
        / / this method inherited from Activty and define the way into the final, don't let subclasses implementation, but provide onRetainCustomNonConfigurationInstance method
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
        // Fragments are the data that all fragments of the current activity need to store
        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        / / mViewModelStore and fragments could NonConfigurationInstances do exist
        return nci;
    }

    protected void onCreate(@Nullable Bundle savedInstanceState) {... NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();/ / call getLastNonConfigurationInstance method to obtain NonConfigurationInstances restore data
        if(nc ! =null) {
            mViewModelStore = nc.viewModelStore;
        }
        if(savedInstanceState ! =null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc ! =null ? nc.fragments : null);
            / / nc. Fragments could pass mFragments. RestoreAllState ()}... }Copy the code

Here onRetainNonConfigurationInstance and getLastNonConfigurationInstance methods are not deep, Configuration change also want to save some custom object data can be rewritten onRetainCustomNonConfigurationInstance to achieve them. ? (that is, the fragments of mViewModelStore mFragments. RetainNestedNonConfig () and mFragments restoreAllState () these two methods. MFragments are FragmentController instances and are finally called to the FragmentManager, which is actually implemented as the FragmentManagerImpl. Here’s a snippet of the FragmentManagerImpl key code.

/ / this method is the Activity mFragments retainNestedNonConfig () call the method in the end
FragmentManagerNonConfig retainNonConfig(a) {
    setRetaining(mSavedNonConfig); // For space reasons, this method is not expanded in this article
    return mSavedNonConfig;
}
/ / to see mSavedNonConfig where the instance is created, back to FragmentActivity. Found in onSaveInstanceState method call mFragments. SaveAllState (save) state, Finally, the FragmentManagerImpl saveAllState()
Parcelable saveAllState(a) {
    mSavedNonConfig = null;
    saveNonConfig();
}    

void saveNonConfig(a) {
    ArrayList<Fragment> fragments = null;
    ArrayList<FragmentManagerNonConfig> childFragments = null;
    ArrayList<ViewModelStore> viewModelStores = null;
    if(mActive ! =null) {
        //mActive = SparseArray
      
        for (int i=0; i<mActive.size(); i++) {
            Fragment f = mActive.valueAt(i);
            if(f ! =null) {...if(viewModelStores ! =null) {
                     // Add the fragment member mViewModelStore to the listviewModelStores.add(f.mViewModelStore); }}}}if (fragments == null && childFragments == null && viewModelStores == null) {
        mSavedNonConfig = null;
    } else {
        //viewModelStores is stored in FragmentManagerNonConfig
        mSavedNonConfig = newFragmentManagerNonConfig(fragments, childFragments, viewModelStores); }}/ / mFragments restoreAllState () call FragmentManagerImpl restoreAllState eventually ()
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    // The FMS is stored in Parcelable when the state is saved
    if (fms.mActive == null) return; 
    // There are many different scenarios. Here is a general analysis of the common recovery process. mActive =new SparseArray<>(fms.mActive.length);
    / / traverse FragmentState
    for (int i=0; i<fms.mActive.length; i++) { 
        FragmentState fs = fms.mActive[i];
        if(fs ! =null) {... ViewModelStore viewModelStore =null;
            if(viewModelStores ! =null && i < viewModelStores.size()) {
                viewModelStore = viewModelStores.get(i);
            }
            Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig,
                                        viewModelStore);
            // Pass in the viewModelStore and call the instantiate method to regenerate the Fragment
            if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ":"+ f); mActive.put(f.mIndex, f); }}}Copy the code

What is LiveData and how does it work

LiveData is a holder of observable data, but unlike ordinary observable data, LiveData is bound with life cycle, such as the life cycle of Activitie and Fragment. A little abstract, but let’s look at the key classes first.

// An Observer is an interface. Call onChanged when the data changes
public interface Observer<T> {
    void onChanged(@Nullable T t);
}

// The LiveData class is abstract, so what methods are exposed
public abstract class LiveData<T> {
    // Add an observer view, passed in LifecycleOwner, which is used to bind the lifecycle
    public void observe(LifecycleOwner owner, Observer<T> observer)
    // Add observer observation, but there is no LifecycleOwner
    public void observeForever(Observer<T> observer) 
    // Remove the observer
    public void removeObserver(Observer<T> observer) 
    // Remove all observers from a LifecycleOwner
    public void removeObservers(final LifecycleOwner owner)
    // Whether there is an observer
    public boolean hasObservers(a)    
    // Whether there is a live observer
    public boolean hasActiveObservers(a)
}    
//MutableLiveData inherits LiveData and opens 2 more methods.
public class MutableLiveData<T> extends LiveData<T> {
    public void postValue(T value) // Post a data
    public void setValue(T value)  // Set a data
    The difference between postValue and setValue is that post can be used in child threads, while setValue can only be called in the UI thread
}
Copy the code

Having covered the main classes, here’s an example:

This code is usually in an Activity or Fragment that uses liveData to add an observer. // When the User data changes, The onChanged method is called to update the UI livedata.observe (this, New Observer<User>() {@override public void onChanged(@nullable User) {// update UI}}); // The following code is usually in the ViewModel, where the liveData is the same as the liveData above // can be postValue orsetThe onChanged method is called back when the user data is changed. // If the user data is retrieved via a network request in the ViewModel, LiveData = new MutableLiveData<User>() livedata.postValue (User)// orsetValue(user)
Copy the code

Here is a look at LiveData source to understand the principle

How LiveData works

This section uses the data communication between Observe (LifecycleOwner owner, Observer Observer) and postValue as an example. LifecycleOwner is not familiar with you can see my last article :LifecycleOwner, below look at livedata.observe source code:


	@MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        They are DESTROYED. Add observer is meaningless
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        
        
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        //LifecycleBoundObserver inherits ObserverWrapper, and LifecycleBoundObserver implements LifecycleOwner
        //mObservers this is a Map, key is observer, value is ObserverWrapper
        if(existing ! =null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if(existing ! =null) {
            return;
        }
        // Add the lifecycle observer
        owner.getLifecycle().addObserver(wrapper);
    }
Copy the code

Then look at the LifecycleBoundObserver and ObserverWrapper fragments:

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;
        
        @Override
        boolean shouldBeActive(a) {
            // At least STARTED
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        / / GenericLifecycleObserver inherited from LifecycleObserver, onStateChanged will be called back when the life cycle changes
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            // Remove observer while you DESTROYED the lifecycle and pass in liveData. observe when mObserver is passed in
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            //shouldBeActive() is implemented in the current class, isAtLeast(STARTED), at least is true for the STARTED state
            // The life cycle is true if it is in the STARTED and RESUME states
            // The parent class activeStateChanged is then calledactiveStateChanged(shouldBeActive()); }}private abstract class ObserverWrapper {
        
        void activeStateChanged(boolean newActive) {
            // Return immediately if the mActive status is the same
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            // mActive is true for STARTED and RESUME, and false for others
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            // mActive=true increases mActiveCount by 1; mActive=false decreases mActiveCount by 1.
            // Why do we add and subtract 1 here? When activity.oncreate -> activity.onstart -> activity.onresume, The State is CREATED->STARTED->RESUMED. When STARTED,mActive is true,mActiveCount is 1, and then RESUMED, mActive is still true, If (newActive == mActive) returns, mActiveCount is still 1.
            / / when the Activity onResume - > Activity. The onPause - > Activity. The onStop, The RESUMED->STARTED->CREATED, Activity. OnStop State is CREATED, isAtLeast(STARTED) is false, and mActive=false MActiveCount minus 1, mActiveCount is 0.
            OnResume, State from CREATED->STARTED->RESUMED, MActive = true,mActiveCount = 1, mActiveCount = 1.
            if (wasInactive && mActive) {
                // Based on the above analysis, onActive will be called when the Activity first displays the UI call and then again when it returns from the background or after another Activity. (Open the transparent Activity without going onStop, except in this case)
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                // activity. onStop is called after
                onInactive();
            }
            if (mActive) {
                Let's consider the dispatchingValue method and let it be notified by liveData.duties
                dispatchingValue(this); }}}private void considerNotify(ObserverWrapper observer) {
        if(! observer.mActive) {//mActive can know if it is in onStop state. If it is in Stop state, do not distribute data
            return;
        }
        if(! observer.shouldBeActive()) {//shouldBeActive() again checks if the observer is alive
            //ObserverWrapper implementation class is more than LifecycleBoundObserver
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            //mVersion will +1 when calling postValue() or setValue()
            return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); Call onChanged to distribute changed data}Copy the code

The postValue method is used to pass the value to onChanged:

 
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) { 
        NOT_SET (NOT_SET is an Obj object);
        // Look down with questions
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if(! postTask) {// See below, and explain the locking problem
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    // mPostValueRunnable runs on the main thread
}

private final Runnable mPostValueRunnable = new Runnable() {
    @Override
    public void run(a) {
        Object newValue;
        synchronized (mDataLock) {  PostValue = postValue; postValue = postValuenewValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); }};@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++; // Increase the version number by 1 to ensure that mVersion>mLastVersion
    mData = value; // Assign to mData and the data will be distributed to mobServer.onchanged
    dispatchingValue(null);
    
    PostValue and mPostValueRunnable locks, as well as NOT_SET
    //1. The postValue method may be called by many threads
    MPostValueRunnable runs asynchronously on the main thread, which is executed in series, one runnable at a time
    // if the postValue method is used to assign a value to mPendingData, the postValue method is used to assign a value to mPendingData.
    // Assign mPendingData to mData in the mPostValueRunnable main thread,
    // Both mPendingData and mData refer to the same object. MData refers to the latest object in the main thread.
    // In the main thread queue, mPostValueRunnable takes the latest data every time it runs, which is a bit redundant
    //4. If NOT_SET is removed, value is assigned from postValue to mPendingData.
    // mPendingData and mData refer to the same object.
    // Add a lock. But with NOT_SET, the address of mPendingData is given to mData, the address of NOT_SET is given to mPendingData,
    // In the child thread of the postValue method, the new value address is assigned to mPendingData.
    //5. Look at the postValue method if (! PostTask) {return} this place,
    // If thread A calls postValue, assigns value to mPendingData and releases the lock,
    // add a.postValuerunnable to the Message of the main thread.
    PostValueRunnable (b.mpostValuerunnable) has not yet been run to setValue and does not hold the lock.
    // in this case, thread B also calls postValue, while a.postValuerunnable is not running yet. MPendingData =NOT_SET
    // the B thread postTask is false, and then it assigns value to mPendingData, and then it returns.
    // While a.postValuerunnable is running, mPendingData is updated by thread B.
    // if (! PostTask) {return} This judgment can reduce the main thread does not need to run mPostValueRunnable,
    // And can be updated to the latest data.
    
}

private void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // Initiator is null, the following is a loop through the mObservers store ObserverWrapper to notify all observers.for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
         mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
        considerNotify(iterator.next().getValue()); 
        View Livedata. observe and let data observe.onchanged}}Copy the code

ViewModel and LiveData are used together

A few notes about using ViewModel and LiveData:

  • Viewmodels should not hold life-cycle objects such as activities, fragments. It’s better not to hold the context
  • You can use AndroidViewModel to have application objects
  • Do not have LiveData objects. LiveData is best stored in a ViewModel that will not be destroyed due to configuration changes
  • Do not have data objects in your activity or fragment. When updating your UI in observer. onChange, do not store data in your activity or fragment
public class UserActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { ...  findViewById(R.id.btn_logout).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mViewModel.onLogout(); // Click the button to log out}}); mViewModel = ViewModelProviders.of(this).get(UserViewModel.class); //ViewModelProviders is a utility class. The default contain AndroidViewModelFactory UserViewModel. Class create ViewModel through reflection and stored in the Activity of mViewModel. GetUserLiveData (). Observe (this, New Observer<User>() {@override public void onChanged(@nullable User User) {// Update UI in Observerif(user ! = null){ mTvName.setText(user.name); }else{// No login status mtvname.settext ("User has not logged in to!!!!!"); }}}); mViewModel.loadUserInfo(); }} Public class UserViewModel extends ViewModel {//userLiveData is stored in UserViewModel private Final MutableLiveData<User> userLiveData = new MutableLiveData<User>(); public MutableLiveData<User>getUserLiveData() {
        return userLiveData;
    }
    @Override
    protected void onCleared() {// Release resources} public voidloadUserInfo() {
        new Thread(new Runnable() {
            @Override
            public void run() { try { Thread.sleep(1000); New Thread User User = new User(); if (InterruptedException = new User(); if (InterruptedException = new User(); user.name ="Albert"; userLiveData.postValue(user); }}).start(); } public voidonLogout() {
        new Thread(new Runnable() {
            @Override
            public void run() { try { Thread.sleep(1000); } Catch (InterruptedException ignored) {} / Start a thread asynchronously userLivedata.postValue (null); }}).start(); }}Copy the code

After introducing examples, here are the advantages:

  • Data updates notify UI updates, and data updates are associated with a life cycle to avoid updating the UI during onStop.
  • No manual handling of the life cycle is required
  • No memory leaks
  • Configuration changes, previously loaded data can still be used
  • Fragments can share viewModels, livedata, etc. (this is not expanded, but will be shared later)

Reference:

  • Developer. The android. Google. Cn/topic/libra…
  • Developer. The android. Google. Cn/topic/libra…