preface
As an architectural mode, MVVM is mainly implemented in Android without two core classes, LiveData and ViewModel. You should have basic knowledge of LiveData and ViewModel before reading.
You may be more productive if you read this article with a clear goal in mind. This article can help you clear up those doubts
- How does a ViewModel share data between fragments?
- How does the ViewModle survive the Activity switch?
- How does the ViewModleScope sense the component lifecycle and commit suicide (cancel)?
- How does LiveData protect against memory leak risk?
- What happens when LiveData’s observer is active?
- If a child thread sends values to LiveData multiple times in a row, does the Observer receive all of them?
1) the ViewModel
The ViewModel is responsible for storing and managing data related to the interface in a life-cycle oriented manner
1.1) the ViewModel properties
1.1.1) Focus on the life cycle
- The ViewModel focuses on the component lifecycle mainly because it is not destroyed and rebuilt when the Activity is rebuilt due to configuration changes such as vertical and horizontal transitions. In other words, the lifecycle of a ViewModel is longer than the lifecycle of the activities/fragments it serves. You can check out my other article if you want to see what we’re talking about when we talk about the Android rebuild mechanism.
1.1.2) ViewModel related classes
a) ViewModelStoreOwner
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore(a);
}
Copy the code
- Provides and manages the ViewModelStore, saving the ViewModelStore before it is rebuilt due to configuration changes.
- Call the ViewModelStore clear() method when Destroy is destroyed
b) ViewModelStore
// There are deletions, pseudo code
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if(oldViewModel ! =null) { oldViewModel.onCleared(); }}final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear(a) {
for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
- The ViewModelStore has a Map that stores the ViewModel;
- Provides methods for getting, storing, and emptying viewModels externally.
- The ViewModel clear () method is called when clear.
c) ViewModel
public abstract class ViewModel {
private final Map<String, Object> mBagOfTags = new HashMap<>();
final void clear(a) {..synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
if (obj instanceof Closeable)
((Closeable) obj).close();
}
}
onCleared();
}
<T> T setTagIfAbsent(String key, T newValue) {... }
<T> T getTag(String key) {... }}}Copy the code
- A Map class mBagOfTags is maintained internally to track data related to the ViewModel object, such as viewModelScope and SavedSateHandle
- In addition, when the clear() method is called, execute its close method on the value of the Closeable interface iterated from mBagOfTags. It also calls its own onClear() method
1.2) ViewModel construction
Usually we use
/ / pseudo code
public <T extends ViewModel> T get(Class<T> class){
ViewModel viewModle = viewModelStrore.get(key)
if(viewModel == null){
viewModle = factory.create(class)
viewModelStore.put(key.viewModel);
}
return viewModel
}
flowViewModel = ViewModelProvider(this).get(FlowViewModel::class.*java*)
Copy the code
- ViewModelProviderl two member attributes, ViewModeStore and ViewModelProvider Factory.
- Each ViewModle generated by default is assigned a key (“
androidx.lifecycle.ViewModelProvider.DefaultKey
+modelClass.*canonicalName
“*) - Reflection calls the constructor of ViewModel, and the ViewModelStoreOwner gets the ViewModelStore as a cache, which enables sharing of viewModels within a ViewModelStoreOwner scope. That is, multiple components share a ViewModel.
- The end result is that only one ViewModel will be created within a ViewModelStoreOwner scope.
- What kind of Factory: KeyFactory,
OnRequeryFactory
,AndroidViewModelFactory
1.3) ViewModel is combined with Kotlin’s Coroutine
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner s,Lifecycle.Event e) {
if (e == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
Copy the code
-
Fragment/Activity both implement the ViewModelStoreOwner interface. ViewModelStroe’s clear() method is called when the component lifecycle state switches to ON_DESTORY. Here is a isChangeingConfigurations () judgment. When an Activity is rebuilt due to a configuration change, data in the ViewModelStore is not cleared. The ViewModelStore is reused when a new Activity is created.
-
When its clear() method is called, its close method is called by iterating through the internal Closeable value. Where viewModelScope is an extended property of ViewModel, as shown below
-
When the ViewModelScope is retrieved, the CloseableCoroutineScope object is added to the ViewModle member property mBagOfTags Map. Meanwhile the CloseableCoroutineScope implements the Closeble interface. Coroutinecotext.cancel () is called when ViewModle clear to remove the coroutine Job.
2) LiveData
- The connection between the ViewModel and the View layer depends primarily on LiveDataI(or Flow). The View layer holds a reference to the ViewModel, but the ViewModel does not hold a reference to the View. This benefit is also obvious. The View layer and the ViewModel layer are decoupled; Improve the testability of the ViewModel.
- See the next section (2.1) for the specific internal implementation. The View layer gets the liveData of the ViewModel layer and registers the observer. When the liveData changes, the UI is automatically updated.
- Memory leaks are naturally prevented by automatic deregistration (see below)
2.1) How is automatic unregistration implemented?
How to register an observer before looking at automatic unregistration
@mainThread public void observe(LifecycleOwner owner, Observer<? super T> observer) { LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); owner.getLifecycle().addObserver(wrapper); }Copy the code
LiveData’s Oberver() method does two main things,
- Save the Observer to its own mObservers property of type Map.
- The Observer is wrapped as a LifecycleBoundObserver, which inherits the LifecycleEventObserver, so the Observer will also be registered with livefeCycle.
So the LifecycleBoundObserver object can look at both events that listen to LiveData and events that LifeCycle. This will allow processing to be done within the LifeCycle event. Such as removing the slave registry, not notifying the callback when inactive.
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {...@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (currentState == DESTROYED) {
ObserverWrapper removed = mObservers.remove(observer); // Unbind from LiveData
removed.detachObserver() // Bind to lifecycle components}... }@Override
void detachObserver(a) {
mOwner.getLifecycle().removeObserver(this); }}Copy the code
2.2) What happens when the observer is active?
- The LiveData listener Observer is internally wrapped as a LifecycleBoundObserver as described in the previous section. The listener is notified of call-back only if the lifecycle component that the Observer listens for is ON_START or ON_RESUME.
The internal implementation is also relatively simple, mainly relying on the Observer to listen to the component life cycle, itself to determine whether it is active.
- One question, does an Observer have to be called back if it goes from inactive to active? The answer is not necessarily.
The reason is that liveData maintains a version internally, and when restored to active state, if the version in Oberver is smaller than that of liveData, the most recent value is distributed to non-Observers and the callback method is triggered. The version in liveData will automatically +1 every time a new value is set, as shown below. That is, if no new values are set to liveData while the Observer is inactive, the Observer will not be notified of a callback when it returns to active.
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
Copy the code
- Another question is, since it can’t be called back again, what’s going on with the data dump online?
2.3) If a child thread sends values to LiveData several times in a row, does the Observer receive all values?
fun postValueIntWorkThread(a){
liveData.post(1)
liveData.post(2)}Copy the code
LiveData has a philosophy of keeping only the most recent values. There are two things
- When a value is posted to liveData from a child thread multiple times in a short period of time, the Observer does not guarantee that each value will be notified. The specific implementation code is as follows:
- When the Observer is inactive, values are set multiple times in liveData. When liveData is active again, only the latest values will be received.
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run(a) {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;/ / comment 3
mPendingData = NOT_SET;/ / comment 4} setValue((T) newValue); }};protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;/ / comment 5
mPendingData = value;// At comment 2, value is assigned to mPendingData
}
if(! postTask) {// Note 1: a value is being distributed
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
Copy the code
This code is interesting
-
1> Comment 5, enter the critical region defined by the monitor mDataLock; Calculate the value of postTask. If postTask is true, there is no value waiting to be distributed. If postTask is false, there is a value waiting to be distributed.
-
2> Comment 2 assigns the value of value to mPendingData regardless of whether postTask is true or false.
-
3 > Comment 3, if there are values being distributed, the next operation is not performed. At this point, comment 2 has assigned the value to mPendingData. Can the mPending still be distributed?
-
This logic is finally executed in the Main thread via the handler mechanism, assigning mPendingData to newValue, which is eventually distributed
After the previous step, consider the case where the postTask is false if the mPendingData value is still 1 while the postTask value is 2. But the value is assigned to mPendingData anyway. Next, comment 1 happens to meet the criteria and does not proceed down. When the main thread executes to the runable at comment 3, it copies 2 to newValue and eventually distributes the value.
summary
This article mainly focuses on ViewModel and LiveData features and internal implementation. After reading, can you find the answer to the first few questions?