What is a ViewModel
First, take a look at Google’s official explanation:
The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation.
Excerpted from the Android Developer website
So how does the ViewModel do that?
The ViewModel object exists for a time Lifecycle that is passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel will remain in memory until Lifecycle is gone permanently: for an Activity, when the Activity completes; For fragments, this is when the Fragment is separating.
What are ViewModelProvide, ViewModelStore, ViewModelStoreOwner?
First, ViewModelStroeOwner holds ViewModelStore:
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore(a);
}
Copy the code
The ViewModelStore holds the ViewModel map:
public class ViewModelStore {
// Hold the ViewModel mapping
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);
}
Set<String> keys(a) {
return new HashSet<>(mMap.keySet());
}
/** * Clears internal storage and notifies ViewModels that they are no longer used. */
public final void clear(a) {
for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
The key in the HashMap that holds the ViewModel is DEFAULT_KEY+ the name of the string, which is specified in viewModelProvid.java:
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey";
@NonNull
@MainThread
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);
}
Copy the code
Whenever you want to get a ViewModel, you need to pass a ViewModelStoreOwner to ViewModelProvide, which gets the ViewModelStore held in ViewModelStoreOwner, Then get the ViewModel in mMap.
What exactly is LiveData
As usual, here’s what the official says:
LiveData is an observable data store class. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the lifecycle of other application components such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state.
What are the advantages of LiveData?
-
Ensure that the interface conforms to the data state
@SuppressWarnings("WeakerAccess") /* synthetic access */ void dispatchingValue(@Nullable ObserverWrapper initiator) { if (mDispatchingValue) { mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; if(initiator ! =null) { considerNotify(initiator); initiator = null; } else { for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; }}}}while (mDispatchInvalidated); mDispatchingValue = false; } Copy the code
Following the Observer mode, LiveData notifies the Observer when data changes and helps you update the page.
-
Memory leaks do not occur
@Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { removeObserver(mObserver); return; } Lifecycle.State prevState = null; while (prevState != currentState) { prevState = currentState; activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); } } Copy the code
In LiveData, an observer is bound to a Lifecycle object and cleans itself up after its associated Lifecycle is destroyed.
-
It does not crash when the Activity stops
@SuppressWarnings("unchecked") private void considerNotify(ObserverWrapper observer) { if(! observer.mActive) {return; } // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. // // we still first check observer.active to keep it as the entrance for events. So even if // the observer moved to an active state, if we've not received that event, we better not // notify for a more predictable notification order. if(! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); } Copy the code
If the observer is not active, it will not receive events from LiveData.
-
You no longer need to handle the life cycle manually
Interface components simply observe relevant data and do not stop or resume observation. LiveData automatically manages all of these operations because it can sense the associated lifecycle state changes as it observes.
-
Data is always up to date
An observer will receive the latest version of data from inactive to active or recreated, and LiveData will record the updated version number.
private abstract class ObserverWrapper { final Observer<? super T> mObserver; boolean mActive; // Final version number int mLastVersion = START_VERSION; ObserverWrapper(Observer<? super T> observer) { mObserver = observer; } abstract boolean shouldBeActive(a); boolean isAttachedTo(LifecycleOwner owner) { return false; } void detachObserver(a) {}void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; } // immediately set active state, so we'd never dispatch anything to inactive // owner mActive = newActive; changeActiveCounter(mActive ? 1 : -1); if (mActive) { dispatchingValue(this); }}}Copy the code
EventBus isn’t working well
First of all, it’s a false statement. There is no denying that EventBus is a good framework, and the idea of publishing and subscribing messages has influenced many of the frameworks in subsequent applications.
But EventBus has some problems:
- Registration and logout are required and there is a risk of memory leaks
- Sending messages increases exponentially, increasing the probability of errors
- Locating the message sending location is complicated
- Each event is a class and is difficult to maintain
Why not use Meituan’s LiveDataBus
LiveDataBus
LiveData is a LiveData based message bus that can be used instead of EventBus, but why not use it directly?
First of all, Bus concept is a message transfer station. In the situation of multi-person collaboration and complex pages, there will be abuse, and the constraint of unique trusted source is missing.
The only reliable source here comes from the Relearn Android blog.
The concept of “unique trusted source” is mainly for scenarios such as “state synchronization between pages”.
In Jetpack MVVM, unique trusted sources are primarily direct holders of LiveData subclasses in the presentation layer or domain layer, such as ViewModel, etc.
LiveData setValue and postValue are protected by default, meaning that for scenarios such as “state synchronization requests”, Allows developers to “restrict ViewModel/Request to Activity/Fragment by exposing its parent LiveData class with restricted access, instead of multable EliveData as a subclass”,
In this way, the data on the page is read-only and comes from the same source. The Activity/Fragment can only use the data it receives to assign values to the mutable states in the ViewModel bound to the view. That is, these mutable states are only for internal use and can only be changed as notified parties. For the source of read-only data, they have no right to tamper, such as the need for new data, can only unilaterally initiate a request to the trusted source, and then by the trusted source unilaterally, unified distribution of results, so through the realization of “read and write separation” to achieve the consistency of multi-page message synchronization
Second, all messages in LiveDataBus are global without distinction. In a componentized project, messages need to be distinguished between those that need to be used only within the component and those that need to be used globally.
What are the benefits of OneStepMessage
OneStepMessage is based on LiveData and ViewModel, so it has all the advantages of LiveData:
- Ensure that the interface conforms to the data state
- Memory leaks do not occur
- It does not crash when the Activity stops
- You no longer need to handle the life cycle manually
- Data is always up to date
OneStepMessage has the following advantages over EventBus and LiveDataBus:
-
Module messages can be written to control the scope of messages
Creating a ViewModel in a single module allows messages to be scoped in a single module. Creating a ViewModel in a public module allows messages to be global and controllable.
-
The business writes the review logic, controls the entry point for sending messages, and has a unique trusted source for sending messages
At the time of creation of each message, a business should pass in its own review code to control message sending. All messages sent by this message should be reviewed to determine the only trusted source.
class DemoViewModel : ViewModel(a){ // Add audit code to control the unique trusted source, all messages can be business control whether to send val message1 = EventLiveData(object : EventLiveData.PostEventReview<String> { override fun review(value: String): Boolean { //TODO message send check code, return true check can send, return false check can not send return true } }) val message2 = EventLiveData(object : EventLiveData.PostEventReview<Bean> { override fun review(value: Bean): Boolean { //TODO message send check code, return true check can send, return false check can not send return false}})}Copy the code
In EventLiveData. In the class:
fun postEventValue(value: T) { if(! postEventReview.review(value)) {"Message review failed, refused to send message, please check review code and message content".showLogWithPosition(javaClass.simpleName) return } "post message : ${value.toString()} ; ".showLogWithPosition(javaClass.simpleName) super.postValue(Event(value)) } Copy the code
-
Based on the Jetpack library, there is no need to introduce other tripartite libraries
-
No need to register, cancel, reduce code risk
-
Log location, each message can be directly located to the location of the sending code, convenient debugging
Improvement direction of the next version:
Refer to Okhttp to optimize the review code logic using the chain of responsibility pattern.
OneStepMessage specific usage method
As the name implies, the framework is called one-step messaging, so it is used in a one-step manner:
-
Create a ViewModel to control whether it is a module message or a global message.
class DemoViewModel : ViewModel(a){ // Add audit code to control the unique trusted source, all messages can be business control whether to send val message1 = EventLiveData(object : EventLiveData.PostEventReview<String> { override fun review(value: String): Boolean { //TODO message send check code, return true check can send, return false check can not send return true } }) val message2 = EventLiveData(object : EventLiveData.PostEventReview<Bean> { override fun review(value: Bean): Boolean { //TODO message send check code, return true check can send, return false check can not send return false}})}Copy the code
-
One line of code sends a message, one line of code subscribes to a message.
findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view -> // Send a message 1 OSM.with(DemoViewModel::class.java).message1.postEventValue("Change message1 random = ${(0.. 100).random()}")}// Listen for message 1 OSM.with(DemoViewModel::class.java).message1.observeEvent(this, ViewModelStore()){ Toast.makeText(this,it,Toast.LENGTH_SHORT).show() } Copy the code
-
For Java, CallBack optimization was added to improve the writing experience.
findViewById(R.id.button_second_message1).setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { OSM.Companion.with(DemoViewModel.class).getMessage1().postEventValue("Change message1 random ="+ r.nextInt()); }}); OSM.Companion.with(DemoViewModel.class).getMessage1().observeEvent(this.new ViewModelStore(), new EventLiveData.OnChanged<String>() { @Override public void onChanged(String value) { showMessage1.setText(value); }});Copy the code
Pay attention to
-
When a message is subscribed, the ViewModelStore is not passed in
The first one received consumes the message, preventing other observers from receiving the message.
-
The same ViewModelStore is passed in when you subscribe to a message
The first one received will consume the message, causing observers sharing the same ViewModelStore to fail to receive the message.
-
When you subscribe to a message, you pass it to a different ViewModelStore
Messages are received separately and do not affect each other.
/** * Events can only be consumed by one observer */
@MainThread
fun observeEvent(owner: LifecycleOwner, onChanged: (T) -> Unit): Observer<Event<T>> {
// Intercepts delivery eventsval wrapperObserver = Observer<Event<T>>() { it.getContentIfNotHandled()? .let { data -> onChanged.invoke(data) } }// Register events
super.observe(owner, wrapperObserver)
return wrapperObserver
}
/** * Events can be consumed by multiple observers, each observer can consume only once */
@MainThread
fun observeEvent( owner: LifecycleOwner, viewModelStore: ViewModelStore, onChanged: (T) -> Unit
): Observer<Event<T>> {
// Intercepts the delivery event to determine whether the observer needs to deliverval wrapperObserver = Observer<Event<T>>() { it.getContentIfNotHandled(viewModelStore)? .let() { data -> onChanged.invoke(data) } }// Register events
super.observe(owner, wrapperObserver)
return wrapperObserver
}
Copy the code
fun getContentIfNotHandled(a): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/** * Each observer records the distribution status * if the observer has not delivered data * otherwise returns NULL */
fun getContentIfNotHandled(viewModelStore: ViewModelStore): T? {
return if (map.contains(viewModelStore)) {
null
} else {
map[viewModelStore] = true
content
}
}
Copy the code
Thank you
Flywith24 KunMinX
warehouse
OneStepMessage