1. What is LiveData
The LiveData component is Jetpack’s new observer-based message subscription/distribution component with host Activity/Fragment lifecycle awareness, which ensures that LiveData only distributes messages to active observers. Only active observers can receive messages.
Features:
- LiveData knows the state of the UI interface and does not trigger unnecessary interface updates if the activity is not displayed on the screen. If the activity has been destroyed, it automatically clears the connection to the Observer so that unexpected calls do not occur.
- LiveData is a LifecycleOwner that directly senses the life cycle of an activity or fragment.
Active: Normally, the host of the Observer is in the started, resumed state, or is still active if you registered with observeForever.
LiveData’s message distribution mechanism is unmatched by Handler, EventBus, and RxJavaBus, regardless of whether the current page is visible or not. The result is that the application does useless work to grab resources even when the background page is not visible. For example, careful students will notice that the list of wechat messages is updated only when the status is visible on the page.
The emergence of LiveData solves the problems of NPE, life cycle transgression, background tasks preempting resources and so on that may be caused by callback callback in the past.
Take a look at how LiveData differs from a traditional message distribution component from a code perspective:
class MainActivity extends AppcompactActivity{
public void onCreate(Bundle bundle){
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
// Perform page refresh,IO, whether the page is visible or not. There are even pop-up dialog boxes}};//1. This message is distributed whether the current page is visible or not. ---- consumes resources
//2. This message is distributed regardless of whether the current host is alive or not. -- Memory leak
handler.sendMessage(msg)
liveData.observer(this.new Observer<User>){
void onChanged(User user){}}//1. Reduce resource usage -- no message will be sent when the page is not visible
//2. Make sure the page is always up to date - the latest message is sent to all observers as soon as the page is visible - keep the page up to date
//3. No more manual handling of the life cycle -- avoid NPE
//4. It is possible to build a message bus that does not de-register and does not leak memory instead of EventBusliveData.postValue(data); }}Copy the code
2. The advantage of LiveData
1. Ensure that the page conforms to the data status
LiveData follows the observer pattern. When the life cycle state changes, LiveData notifies the Observer and sends it the latest data. Observers can update the interface whenever they receive an onChanged event, rather than immediately every time the data changes.
2. No more manual handling of the life cycle
Just look at the data. Do not manually stop or resume observation. LiveData automatically manages unregistration of the Observer because it senses changes in the host life cycle and automatically unregisters onDestory in the host life cycle. Because there is no memory leak with LiveData for message distribution.
3. Keep the data up to date
If the host’s life cycle becomes inactive, it receives the latest data when it becomes active again. For example, an Activity that was once in the background receives the latest data as soon as it returns to the foreground.
4. Support distribution of sticky events
This means sending a piece of data and then registering an observer. By default, the observer will receive the data sent before
5. Share resources
We can extend LiveData using the singleton pattern to implement a global message distribution bus
3. The use of LiveData
Dependencies need to be added before using LiveData:
// Usually, just add appCompat
api 'androidx. Appcompat: appcompat: 1.1.0'
// If you want to use it alone, you can introduce the following dependencies
api 'androidx. Lifecycle: lifecycle - livedata: 2.0.0'
Copy the code
Implementation of LiveData
1.MutableLiveData
We need to use this subclass when we use LiveData for message distribution. The reason for this design is to take into account the single open and closed principle, only when the MutableLive object can be sent messages, Livedata object can only accept messages, to avoid the chaotic use of both sending and receiving messages when the Livedata object is obtained.
public class MutableLiveData<T> extends LiveData<T> {
// execute in child thread
@Override
public void postValue(T value) {
super.postValue(value);
}
// execute in main thread
@Override
public void setValue(T value) {
super.setValue(value); }}Copy the code
2.MediatorLiveData
- Data transmitted by multiple LiveData can be observed for unified processing.
- It can also serve as a LiveData observed by other observers.
// Create two similar LiveData objects
LiveData<Integer> liveData1 = new MutableLiveData();
LiveData<Integer> liveData2 = new MutableLiveData();
// Create an aggregate class MediatorLiveData
MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
// Add the LiveData created above separately.
liveDataMerger.addSource(liveData1, observer);
liveDataMerger.addSource(liveData2, observer);
Observer observer = new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer s) {
titleTextView.setText(s);
}
// Once liveData1 or liveData2 sends new data, the Observer can observe it in order to process the UI update uniformly
Copy the code
The basic use
LifeData is defined. Livedata in the project is generally stored in ViewModel to ensure that data will not be lost when app configuration changes.
1. Define observers to observe data changes in LiveData
// An observer is required to observe the data
Observer observer=new Observer<String>(){
@Override
public void onChanged(String s) { nameTextView.setText(s); }};Copy the code
2. Livedata to subscribe to the observer
public class NameViewModel extends ViewModel{
LiveData<String> currentName = new MutableLiveData();
public LiveData<String> getCurrentName(a){
returncurrentName; }}// Get the viewModel
model= ViewModelProviders.of(this).get(NameViewModel.class);
// Fetch liveData to complete the subscription
model.getCurrentName().observe(this,observer);
Copy the code
3.Livedata sends a message to the Observer to update the data
// The onChanged method in observer is called back
model.getCurrentName().setValue(anotherName);
Copy the code
Transformations. The map operators
You can make changes to the LiveData data and return a new LiveData object.
MutableLiveData<Integer> data = new MutableLiveData<>();
// Data conversion
LiveData<String> transformData = Transformations.map(data, input -> String.valueOf(input));
// Use transformData generated after the transformation to observe data
transformData.observe( this, output -> {
});
// Send data using raw LiveData
data.setValue(10);
Copy the code
LiveData core method
The method name | role |
---|---|
observe(LifecycleOwner owner,Observer observer) | Register observers associated with the host lifecycle |
observeForever(Observer observer) | Registered observer, not anti – registered, need to maintain |
setValue(T data) | Send data, not distributed when there are no active observers, only in the main thread |
postValue(T data) | Like setValue, it is not limited by the thread environment |
onActive | Triggered if and only if there is an active observer |
inActivie | Trigger only when there are no active observers |
4. Implementation principle of LiveData
The message distribution process is as follows:
The message distribution process that is triggered when registering an observer
Register to monitor
Observe During registration, you can bind with the host life cycle
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//1. First assert that this method can only be called on the main thread, as is observeForever.
assertMainThread("observe");
//2. Second, wrap the registered observer as an observer with a living boundary
// It can listen for host destruction events, thus actively unregister itself, to avoid memory leaks
// Whether the observer is active equals whether the host is visible
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
//3. The observation is then checked to see if it has already been registered. If so, an exception is thrown, so note that duplicate registration is not allowed
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if(existing ! =null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
//4. This is the key step
Lifecycle can be monitored by registering an observer with Lifecycle, right?
Lifecycle will also synchronize its state with that of the host once a new observer is added according to the analysis in the previous section. The onStateChanged method for the observer is fired at this point
owner.getLifecycle().addObserver(wrapper);
}
Copy the code
LifecycleBoundObserver listens for the lifecycle of the host and does not distribute any data when the host is not visible
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive(a) {
// Observers registered using the Observer method are wrapped as LifecycleBoundObserver
// Whether the observer is active is equal to whether the host state is greater than or equal to STARTED,
// If the page is not currently visible and you send a message, it will not be distributed at this time, to avoid background tasks to grab resources, and will be distributed when the page is visible again.
// Note: If you use observerForever to register an observer,
// is wrapped as AlwaysActiveObserver, whose shouldBeActive returns true consistently. Data is received even if it is not visible on the page
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
// In this case, if the listener detects that the host is destroyed, it actively removes itself from the Observer of LiveData
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// Otherwise, the state of the host has changed, and the host is determined to be activeactiveStateChanged(shouldBeActive()); }}Copy the code
Data state change
If the observer is active after the ObserverWarpper state changes, the data distribution process will be triggered:
abstract class ObserverWrapper{
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION// This equals -1, no active alignment with the mVersion of LiveData, setting the stage for sticky events
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// Change the state of the observer
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
OnActive is triggered if there is only one active observer
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
// Trigger onInactive if there are no active observers
// When this method is triggered, you can do a lot of things like lazy loading, resource release, etc
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
// If the observer is active, the data will be distributed
// Notice that this = observer is passed
if (mActive) {
dispatchingValue(this); }}}Copy the code
Data distribution
DispatchingValue Controls the data distribution process
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if(initiator ! =null) {
// If the passing observer is not empty, the data is distributed to himself. This process is triggered when a new observer is registered
considerNotify(initiator);
initiator = null;
} else {
Otherwise, let's traverse all the registered observers in the collection and call the notice one by one to distribute the data
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
To be considered as an authentic place to distribute data, three conditions must be met:
private void considerNotify(ObserverWrapper observer) {
//1. The observer is not active and does not distribute
if(! observer.mActive) {return;
}
//2. Whether the host where the observer resides is active, otherwise it is not distributed and changes the observer's state to false
if(! observer.shouldBeActive()) { observer.activeStateChanged(false);
return;
}
//3. Check whether the number of times the observer receives messages is greater than or equal to the number of times the observer sends messages
// But when the Observer was created, verison=-1
// If LiveData has already sent data. This is not enough, there is a sticky event, the post-registered observer received the message sent earlier.
if (observer.mLastVersion >= mVersion) {
return;
}
// Each time a message is distributed, the version of the observer is aligned with that of the LiveData to prevent repeated transmission
observer.mLastVersion = mVersion;
// Final data transfer
observer.mObserver.onChanged((T) mData);
}
Copy the code
Problem: Sticky messages
That is, a newly registered Observer can receive the last data sent previously. The reason is that LiveData’s mVersion is +1 every time it sends a piece of data. But with lastVersion = 0 on the newly-registered Observer, the duties method in the diagram will distribute the previously sent data to the registered Observer.
General message distribution process
SetValue triggers message distribution only when postValue is called:
5.observerForever
We often use Observer () and observerForever() to register observers. What’s the difference?
- Observer () : Manual unregistration is not required, and the host does not receive messages when it is not visible, but receives the latest data as soon as it is visible;
- ObserveForever () : you need to de-register yourself manually and receive messages whether the host is visible or not;
- You can take advantage of the onActive() method being activated to implement some lazy loading of data.
6.LiveDataBus
Now that we know why LiveData generates sticky events (if the previous time was sent, the later registered observer could receive the message)? So how to solve it?
LiveDataBus is very similar to EventBus, but we have the advantage of not having to manually de-register and worry about memory leaks.
LiveDataBus.with("eventName").observer(lifecycleOwner,sticky,new Observer<String>{
void onChanged(String data){}})Copy the code
core idea
The core method of message distribution in LiveData:
void considerNotify(ObserverWrapper observer) {
// If the observer is not active, it is not distributed.
if(! observer.shouldBeActive()) { observer.activeStateChanged(false);
return;
}
// The number of messages received by the observer >= the number of messages sent by LiveData, not distributed.
// A newly registered Observer can receive the last item of data if it has already been sent.
if (observer.mLastVersion >= mVersion) {
return;
}
// The root cause is that the version field of ObserverWrapper was created with =-1 and was not actively aligned with the mVersion field of LiveData
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
Copy the code
The key to controlling sticky events is the version field of the Observer. When registering a new Observer, the mLastVersion field should be actively consistent with the LiveData.mVersion field.
However, the version field above is not available and cannot be modified. There is a scheme on the Internet that uses reflection to forcibly align the mLastVersion of the Observer with livedata.mversion, but it is not elegant enough.
So we use the proxy design pattern to modify the behavior of the newly registered Observer. Here’s the code:
class StickyObserver<T> implements Observer<T> {
private StickyLiveData<T> mLiveData;
private Observer<T> mObserver;
private boolean mSticky;
// Mark that the Observer has received data several times to filter out repeated receipt of old data
private int mLastVersion = 0;
public StickyObserver(StickyLiveData liveData, Observer<T> observer, boolean sticky) {
// Let's say we send a piece of data using StickyLiveData. StickyLiveData#version=1
// When we create WrapperObserver and register it
// The version needs to be the same as the version of StickyLiveData
// To filter old data, otherwise it will receive old data.
mLastVersion = mLiveData.mVersion;
}
@Override
public void onChanged(T t) {
if (mLastVersion >= mLiveData.mVersion) {
// But if the current observer is concerned with sticky events, give it.
if(mSticky && mLiveData.mStickyData ! =null) {
mObserver.onChanged(mLiveData.mStickyData);
}
return; } mLastVersion = mLiveData.mVersion; mObserver.onChanged(t); }}Copy the code
StickyLiveData supports sticky event subscription and distribution, and controls the timing of message distribution itself, which is much more elegant than the way the liveData.mversion field is modified using reflection, which is widely used on the web
public static class StickyLiveData<T> extends LiveData<T> {
private String mEventName;
private T mStickyData;
private int mVersion = 0;
public StickyLiveData(String eventName) {
mEventName = eventName;
}
@Override
public void setValue(T value) {
// Each time a message is sent, the version number needs to be +1, because we need to pass this version control to distribute sticky events.
mVersion++;
super.setValue(value);
}
@Override
public void postValue(T value) {
super.postValue(value);
}
public void setStickyData(T stickyData) {
// Send sticky messages synchronously
this.mStickyData = stickyData;
setValue(stickyData);
}
public void postStickyData(T stickyData) {
// Send messages asynchronously
this.mStickyData = stickyData;
postValue(stickyData);
}
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
observerSticky(owner, observer, false);
}
public void observerSticky(LifecycleOwner owner, Observer<? super T> observer, boolean sticky) {
super.observe(owner, new WrapperObserver(this, observer, sticky));
owner.getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Automatic unregistermHashMap.remove(mEventName); }}}); }}Copy the code
LiveDataBus complete code
object LiveDataBus {
private val eventMap = ConcurrentHashMap<String, StickyLiveData<*>>()
fun <T> with(eventName: String): StickyLiveData<T> {
// Subscribe and distribute messages based on event names,
// Because a liveData can only send one data type
// Therefore, different events need to be distributed using different LiveData instances
var liveData = eventMap[eventName]
if (liveData == null) {
liveData = StickyLiveData<T>(eventName)
eventMap[eventName] = liveData
}
return liveData as StickyLiveData<T>
}
// Get the mVersion field in liveData through a bunch of reflections to control whether sticky data is distributed or not, but we think this reflection is not elegant enough.
class StickyLiveData<T>(private val eventName: String) : LiveData<T>() {
internal var mStickyData: T? = null
internal var mVersion = 0
fun setStickyData(stickyData: T) {
mStickyData = stickyData
setValue(stickyData)
// Send data in the main thread
}
fun postStickyData(stickyData: T) {
mStickyData = stickyData
postValue(stickyData)
// Thread free
}
override fun setValue(value: T) {
mVersion++
super.setValue(value)
}
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
observerSticky(owner, false, observer)
}
fun observerSticky(owner: LifecycleOwner, sticky: Boolean, observer: Observer<in T>) {
// Allows you to specify whether registered observations need to be concerned about stickiness events
//sticky =true, the observer will receive the sticky event message if data has already been sent
owner.lifecycle.addObserver(LifecycleEventObserver { source, event ->
// Listen for host destruction events to actively remove LiveData.
if (event == Lifecycle.Event.ON_DESTROY) {
eventMap.remove(eventName)
}
})
super.observe(owner, StickyObserver(this, sticky, observer))
}
}
class StickyObserver<T>(
val stickyLiveData: StickyLiveData<T>,
val sticky: Boolean.val observer: Observer<in T>
) : Observer<T> {
// The reason for the version alignment between lastVersion and LiveData is to control distribution of sticky events.
// Sticky cannot be equal to true. It can only receive messages sent after registration. If sticky events are to be received, sticky must be transmitted to true
private var lastVersion = stickyLiveData.mVersion
override fun onChanged(t: T) {
if (lastVersion >= stickyLiveData.mVersion) {
// stickyLiveData has no updated data to send.
if(sticky && stickyLiveData.mStickyData ! =null) {
observer.onChanged(stickyLiveData.mStickyData)
}
return
}
lastVersion = stickyLiveData.mVersion
observer.onChanged(t)
}
}
}
Copy the code