This article has participated in the third “topic writing” track at the Diggings Creators Camp. For more details, check out diggings program | Creators Camp.
An overview,
LiveData was co-released as part of the Architecture Component at Google I/O 2017. LiveData is a life-cycle aware, observable container of data. Being able to ensure that data is updated only when the component is active. It can be used not only in combination with the ViewModel, but also on its own. LiveData is designed based on the observer pattern, and only active observers (START and RESUME) are updated; inactive observers are not notified.
Two, introduction and basic use
2.1 introduction of LiveData
Using LiveData has the following advantages:
Ensure that the interface conforms to the data state
LiveData follows the observer pattern. LiveData notifies the Observer object when the lifecycle state changes. You can integrate code to update the interface in these Observer objects. Instead of updating the interface each time the application data changes, the observer can update the interface each time the application data changes.
No memory leaks will occur
The observer is bound to a Lifecycle object and will clean itself up after its associated Lifecycle is destroyed.
It does not crash when the Activity stops
If the observer’s life cycle is inactive (such as an Activity in the return stack), it does not receive any LiveData events.
Manual processing of the lifecycle is no longer required
The interface component simply observes the relevant data and does not stop or resume observation. LiveData will manage all of these operations automatically because it can sense the associated lifecycle state changes as it observes.
The data is always up to date
If the lifecycle becomes inactive, it receives the latest data when it becomes active again. For example, an Activity that was in the background will receive the latest data as soon as it returns to the foreground.
Appropriate configuration changes
If an Activity or Fragment is recreated as a result of a configuration change, such as device rotation, it immediately receives the latest available data.
Shared resources
You can use the singleton pattern to extend the LiveData object to encapsulate system services so that they can be shared across applications. The LiveData object connects to the system service once, and then any observer who needs the corresponding resource needs only to look at the LiveData object. For more information, see Extending LiveData.
2.2 LiveData usage
2.2.1 Basic Usage
//MainActivity.kt mainViewModel.navigateToDetailAction.observe(viewLifecycleOwner, EventObserver {//1 Pokemon -> findNavController().navigate(toPokemonDetail(Pokemon), FragmentNavigatorExtras(cardView to pokemon.url)) }) ..... Omit... //MainViewModel.kt private val navigateToDetailAction = MutableLiveData<Pokemon>() fun openDetail(pokemon: Pokemon) { navigateToDetailAction.value = pokemon //2 }Copy the code
The observe method in note 1 takes two parameters, LifecycleOwner and Observer. The first parameter is the MainActivity itself, and the second parameter creates a new Observer that gets the callback in the onChanged method. The postValue method in the comment updates the data in the main thread so that the printed result is obtained.
Third, the principle of
3.1 LiveData class diagram
As you can see, the LiveData class is quite simple. First, LiveData itself is an abstract class, but it has no abstract methods. It has a LifecycleBoundObserver inner class that implements the LifecyclerEventObserver interface so that it is notified via the onStateChanged method callback when the life cycle changes. It inherits the ObserverWrapper abstract class to handle the configuration of observers. MutableLiveData is an implementation class of LiveData, which internally just sets the setValue and postValue methods to public. Used to indicate that its internal value is variable. MediatorLiveData can be regarded as the agent of multiple LiveData. When multiple LiveData are added to MediatorLiveData, MediatorLiveData receives notification when any of the LiveData changes.
3.2 Source code Parsing
Before source code parsing, the source code is read with the following three questions: 1. How does LiveData know about life cycle changes? 2. How does LiveData call back notifications after calling the observe method? 3. The flow of LiveData’s postValue/setValue method?
We use the most commonly used observe method as the entry point for source parsing
@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? Super T> observer) {//1. You can only add listener assertMainThread("observe") on the main thread; if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } //2. LifecycleBoundObserver Wrapper = new LifecycleBoundObserver(owner, Observer); //3. Add the observer to the Map. The putIfAbsent method returns the value if the current key already exists. ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); // If (existing! = null && ! existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; Owner.getlifecycle ().addobServer (wrapper); //4. }}Copy the code
When the liveData.observer () method is called, the first parameter is passed as LifecycleOwner, and the second parameter, Obserser, is actually the callback after our observation.
The second step is to pass two arguments to the wrapper class, where Lifecycle is monitored and obersrver is operated. The third step stores the observer and the wrapper class above into mObservers. Returns the corresponding value if it already exists in the Map. Step 4: Add to Lifecycle’s observer queue. So that it can sense life cycle changes.
Next, let’s examine the LifecycleBoundObserver wrapper class
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { .... Omit the code @nonnull final LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } @override Boolean shouldBeActive() {// LiveData is active only when it is in the onStart,onResume,onPause life cycles. return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { //1. While Lifecycle is in the destroyed state, the observer is removed. If (mowner.getLifecycle ().getCurrentState() == DESTROYED) {removeObserver(mObserver); return; } //2. Update the LiveData active state activeStateChanged(shouldBeActive()); }... Class void activeStateChanged(Boolean newActive) {// If the status has not changed, If (newActive == mActive) {return; } mActive = newActive; boolean wasInactive = LiveData.this.mActiveCount == 0; LiveData.this.mActiveCount += mActive ? 1:1; If (wasInactive && mActive) {// The default null implementation onActive(); } / / active number from 1 to 0 if the observer (LiveData. This. MActiveCount = = 0 &&! MActive) {// Default empty implementation onInactive(); } if (mActive) { dispatchingValue(this); }}Copy the code
Concrete implementation of dispatchingValue
@SuppressWarnings("WeakerAccess") /* synthetic access */ void dispatchingValue(@Nullable ObserverWrapper initiator) { If (mDispatchingValue) {// If the distribution is invalid, mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; // This method is called in the setValue method. If the initiator is null, all active observers are notified. Otherwise, only the initiator itself is notified. 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; } @SuppressWarnings("unchecked") private void considerNotify(ObserverWrapper observer) { if (! observer.mActive) { return; } if (!);} if (!); observer.shouldBeActive()) { observer.activeStateChanged(false); return; } // Version judgment. If (observer.mLastVersion >= mVersion) {return; if (observer.mLastVersion >= mVersion) {return; } observer.mLastVersion = mVersion; / / call onChanged method told the observer. MObserver. OnChanged (mData (T)); }Copy the code
First, there are two methods to change the value of LiveData, one is setValue(), the other is postValue(). You can tell the difference between the two methods by their names. The postValue method can be called from the child thread, and eventually the setValue method is called from Handler. So let’s look directly at the process of setValue
private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); }}; . Protected void postValue(T value) {Boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (! postTask) { return; } / / will eventually in the main thread ArchTaskExecutor. Call setValue method getInstance () postToMainThread (mPostValueRunnable); } @MainThread protected void setValue(T value) { assertMainThread("setValue"); // add the version value mVersion++; mData = value; // After the setValue call, notify other observers to update the dispatchingValue(null); }Copy the code
Four, common questions
1. Do I receive a callback if I modify the Livedata value during any lifecycle of the Activity? A: No. Using the observer method, only Lifecycle that is in the STARTED state will receive a callback
2. When is LiveData Active? Answer: It is determined by the implementation of the shouldBeActive method in the ObserverWrapper (private class). When listening with the observe() method, the default implementation class is LifecycleBoundObserver
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
Copy the code
3. Why does LiveData not leak memory? Answer: as described in the previous analysis
4. The LiveData feature causes problems. — Sticky events often said on the Internet
//MainViewModel.kt private val navigateToDetailAction = MutableLiveData<Pokemon>() fun openDetail(pokemon: Pokemon) { navigateToDetailAction.value = pokemon } //MainActivity.kt mainViewModel.navigateToDetailAction.observe(viewLifecycleOwner, FindNavController ().navigate(toPokemonDetail(Pokemon), FragmentNavigatorExtras(cardView to pokemon.url)) })Copy the code
When the openDetail method is called and the value in LiveData is changed, the observer in MainActivity is notified. Then pull up a interface, at this time if the point back key, will find immediately jump back to the page, causing the phenomenon of unable to return. This is because active changes from active to inactive when the current interface jumps to another page and from inactive to active when returning to the same page (triggering the onStateChanged () method of LifecycleBoundObserver) so the callback is called at this point. This leads to the second page again. Solution: 1. In this scenario, call LiveData only once. No more notification after one call.
5. What if I want to keep listening regardless of lifecycle changes? A: Add a listener using observeForever. After adding a listener using this method, note that you need to manually remove the listener by calling removeObserver() when destroying it, otherwise you may cause a memory leak
6. Do I get a callback when calling setValue and postValue with the same value? Answer: yes, look at the above source code analysis in detail. It depends on the version, but not on the specific value.
7. Why is LiveData sticky? Let’s say in Activity1, you define the LiveData variable A, and instead of adding an observer to the page, you add an observer to activity2’s onCreate. When you jump to Activity2 after activity1, setValue, or postValue, you’ll notice that the observer callback in Activity2 works. If you look closely at the source code, you will see that because when you open Activity2 and add an observer, the lifecycle changes are met and because observer.mlastversion < mVersion, the onChanged method of the observer is called.
8. Why do I receive multiple callbacks after adding a listener? A: Troubleshooting solution: 1. Check the number of LiveData listeners. Did you accidentally call the observe method multiple times? For example, call observe in onResume. 2. Check whether postValue is used to update data before the listener is added.
Five, the summary
This paper mainly introduces the usage and principle analysis of LiveData. In general, LiveData implements data distribution capabilities based on Lifecycle.