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…