Android Jetpack—LiveData and data pouring

7ea8cfa0dea64306aef3d792bde1292a.jpg

 1.LiveData

LiveData is an observable data storage class provided by the Android Jetpack package. It can be observed by other components by adding observers. Different from ordinary observers, the most important feature of LiveData is that it has life cycle awareness, which follows the life cycle of other application components (such as Activity, Fragment or Service). This awareness ensures that LiveData only updates app component observers that are in an active lifecycle state. For example, if the data is updated in the Activity but the Activity is already in the destroy state, LiveData will not notify the Activity (observer).

In addition, LiveData has many advantages:

①It can call back the corresponding method when the component is in the active state, so as to refresh the corresponding UI, without worrying about memory leaks.

②When the config causes the activity to be recreated, there is no need to manually process the data storage and recovery. It has been packaged for us.

③When the Activity is not in the active state, if you want Livedata to call back the observer's onChange method immediately after calling setValue instead of waiting until the Activity is in the active state to call back the observer's onChange method, you can use the observeForever method, but it must be at the time of onDestroy removeObserver. (If you want to return to the data immediately after setValue or postValue regardless of the life cycle of the page. Then you can use the observerForever() method, which is not much different from observer(). Because AlwaysActiveObserver does not implement the GenericLifecycleObserver interface, it cannot sense life Period. But it should be noted that after using it, you must remember to call the removeObserver() method in the onDestroy() method to stop observing LiveData, otherwise LiveData will always be active, and Activity will never be automatically activated by the system Recycling can cause memory leaks.

It can be used to implement an event bus instead of event bus. )

Recall, do you often encounter such a problem in the project: when the network request result comes back, it is often necessary to determine whether the Activity or Fragment has been destroyed, and update the UI if it is not destroyed. And if you use Livedata, because it will make the corresponding callback when the Activity is in the state of onStart or onResume, so you can handle this problem very well without writing a lot of activity.isDestroyed().

LiveData has two subclasses: MutableLiveData and MediatorLiveData. MutableLiveData encapsulates a single data that needs to be observed, while MediatorLiveData can observe other LiveData. During the development process, subclasses of LiveData are usually used instead of inheriting LiveData.

LiveData is usually used together with ViewModel. ViewModel is responsible for triggering data updates. Updates will be notified to LiveData, and then LiveData will notify observers of the active state. Of course, LiveData can also be used alone.

For example, how to use MutableLiveData:

private void liveDataTest(){

    MutableLiveData<String> mutableLiveData = new MutableLiveData<>();

    mutableLiveData.observe(this, new Observer<String>() {

        @Override

        public void onChanged(String value) {

           //If the data changes, a notification will be received here, and some reactions can be done here, such as updating the interface

        }

     });

    mutableLiveData.setValue("My value has changed");

}

Divided into three steps:

①Create LiveData corresponding to the data type

②Add an observer to establish a connection with Lifecycle

③ Send the corresponding data to update

 

2. LiveData use

① First introduce dependencies

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

②Layout file activity_live_data_test.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <TextView

        android:id="@+id/tv_text"

        android:layout_width="match_parent"

        android:layout_height="50dp"/>

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal">

        <ImageButton

            android:id="@+id/img_button_add"

            android:layout_width="70dp"

            android:layout_height="70dp"/>

        <ImageButton

           android:id="@+id/img_button_subtract"

            android:layout_width="70dp"

            android:layout_height="70dp"   />

    </LinearLayout>

</LinearLayout>

③ Create a ViewModelWithLiveData class that inherits from ViewModel, and declare variables in this class

public class ViewModelWithLiveData extends ViewModel {

    private MutableLiveData<Integer> LikedNumber;  

    public MutableLiveData<Integer> getLikedNumber() {

        if (LikedNumber == null) {

            //LikedNumber is an object type, not a basic data type, so make sure the variable is not empty

            LikedNumber = new MutableLiveData<>();       

            LikedNumber.setValue(0); //Initialize to 0

        }

        return LikedNumber;

    }

    public void addLikedNumber(int n) {

        LikedNumber.setValue( LikedNumber.getValue() + n);

    }

}

④ Add an observation to the variable in viewModelWithLiveData, click the button to achieve +1, -1 operation

public class LiveDataTestActivity extends AppCompatActivity {

    private ViewModelWithLiveData viewModelWithLiveData;

    private TextView tv_text;

    private ImageButton img_button_add;

    private ImageButton img_button_subtract;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView( R.layout.activity_live);

        viewModelWithLiveData = new ViewModelProvider(this).get(ViewModelWithLiveData.class);

        //Add an observation to the variable in viewModelWithLiveData, observe itself, and call the following onChange function if the data changes. The first parameter of observe() is required to be some objects with LifeCycle management functions, such as Activity

        viewModelWithLiveData.getLikedNumb er().observe(this, new Observer<Integer>() {

          @Override

          public void onChanged(Integer integer) { //Call back this function when the LiveData data changes

              tv_text.setText( String.valueOf(integer));

            }

        });

        img_button_add.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                viewModelWithLiveData.addLikedN umber(1);

            }

        });

        img_button_subtract.setOnClickListener( new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                viewModelWithLiveData.addLikedN umber(-1);

            }

        });

    }

}

ac6e22946cbb422a94eec0ed51489137.gif

Moreover, when rotating the screen, switching the system language and other configurations, the data will not be lost.

 

Benefits of LiveData:

① Monitor the life cycle of the interface bound to it. Therefore, using LiveData does not need to manually manage its life cycle.

② The component can respond to the data changes of LiveData in time, and the component can always get the latest data of LiveData. Of course, there is a certain prerequisite for the bound component to respond to LiveData, that is, the LiveData data changes and the component is active. That is to say, even if the LiveData data changes, it does not necessarily respond to the onChanged function, because it must require the interface where the LiveData data is located to be active before responding to the onChanged function.

③The life cycle is automatically unbound, which can be automatically unbound when the component is destroyed, which greatly reduces the probability of memory leaks in the application.

Note: LiveData can indeed solve the problem of memory leaks, but if used improperly, memory leaks will still occur. For example, there is an Activity, the Activity contains a Fragment, and there is a LiveData in the Fragment, so the Fragment refers to LiveData. Then LiveData binds Activity as the owner through the observe method, then at this time, the life cycle of LiveData will be the same as that of Activity. If Fragment is destroyed for some reason at this time, LiveData will not be destroyed because it is referenced by Activity. LiveData should be recycled but cannot be recycled, then LiveData has a memory leak.

 

3.LiveData source code

①observe method

LiveData uses the observe() method to monitor the life cycle of the interface bound to it.

LiveData.java:

@MainThread

public void observe(LifecycleOwner owner, Observer<? super T> observer) {

    assertMainThread("observe"); //This method can only be called on the main thread

    if (owner.getLifecycle().getCurrentState() == DESTROYED) {

        return; //If the owner's life cycle is already in the DESTROYED state, no further execution

    }

    //Encapsulate the observer object passed in from outside into a LifecycleBoundObserver object

    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);

    //Store observer and wrapper into map as key and value respectively

    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);

    if (existing != null && !existing.isAttachedTo(owner)) {

        throw new IllegalArgumentException( "Cannot add the same observer with different lifecycles"); //The same observer cannot observe different LifecycleOwner

    }

    if (existing != null) {

        return;

    }

    owner.getLifecycle().addObserver(wrapper);

}

First, encapsulate the observer object passed in from the outside into a LifecycleBoundObserver object, and the object name is wrapper. In fact, LifecycleBoundObserver is a class that can monitor the life cycle. You can see the construction method of the LifecycleBoundObserver class:

LiveData.LifecycleBoundObserver.java:

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {

    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {

        super(observer);

        mOwner = owner;

    }

    // …………………………

}

public interface GenericLifecycleObserver extends LifecycleEventObserver { }

public interface LifecycleEventObserver extends LifecycleObserver {

    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);

}

It can be seen that LifecycleBoundObserver implements the GenericLifecycleObserver interface, but in fact this interface finally inherits from the LifecycleObserverinterface, LifecycleObserverwhich is lifecyclethe parameter that needs to be passed when calling addObserver when using it. Therefore, the wrapper obtained by the new LifecycleBoundObserver(owner, observer) method is actually a LifecycleObserver.

Going back to the observe method of LiveData, the line of code mObservers.putIfAbsent(observer, wrapper) is called later, the observer is used as the key, and the wrapper is stored in mObservers as the value, and an ObserverWrapper object will be returned. The putIfAbsent method will judge, if the value already exists, return, otherwise return null. So if it returns null, it means that the observer has not been added before, so LifecycleBoundObserver is used as the observer of the owner life cycle.

mObservers is a data structure implemented by a linked list that supports key-value pair storage. At the same time, it supports deleting any element during the traversal process. It can be understood as a collection of observer objects for the time being.

If ObserverWrapper is not null, the observe method will return directly. Under what circumstances is ObserverWrapper not null?

If the observer observer is already in the mObservers list, and the observer already has another owner owner, it will not be null.

This is to prevent the same observer from being bound by multiple LifecycleOwners, that is, the same observer in one LiveData can only be bound to one LifecycleOwner. Otherwise, an exception "Cannot add the same observer with different lifecycles" will be thrown.

At the end of the observe method, the owner.getLifecycle().addObserver(wrapper) method is called to bind the lifecycle of the wrapper and the owner. That is to say, the observer can monitor the change of the life cycle of the interface corresponding to the owner at this time.

②setValue method

The setValue method is used to update the LiveData data, and the observer will be notified when the data is updated.

LiveData.java:

@MainThread

protected void setValue(T value) {

    assertMainThread("setValue"); //required to be called in the main thread, if postValue is used in other threads

    mVersion++; Assist in judging whether the data has been updated

    mData = value;

    dispatchingValue(null);

}

First call assertMainThread to check whether it is in the main thread, then assign the value to the mData variable, and add 1 to the mVersion variable, mVersion is the version of the data, and it is used to mark whether the data has changed. Finally, call the dispatchingValue() method and pass in null to distribute the data to each observer.

into the method:

LiveData.java:

void dispatchingValue(ObserverWrapper initiator) {

    //If data distribution is currently being done, no repeated processing will be done

    if (mDispatchingValue) {

        mDispatchInvalidated = true;

        return;

    }

    mDispatchingValue = true; //Mark event distribution status as true

    do {

        mDispatchInvalidated = false;

        if (initiator != null) {

            //When the observe method is called, trigger the distribution from here

            considerNotify(initiator);

            initiator = null;

        } else {

            //setValue triggers traversal to observe all observers of the LiveData, and distributes setValue events

            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {

                considerNotify( iterator.next().getValue());

                if (mDispatchInvalidated) {

                    break;

                }

            }

        }

    } while (mDispatchInvalidated);

    mDispatchingValue = false;

}

It can be seen that the difference between passing null and not passing null in the dispatchingValue method parameter is that if null is passed, all observers will be notified, otherwise only the incoming observers will be notified.

There is a while loop in the dispatchingValue method. In the while loop, because the parameter initiator is null, all the else branches will notify all observers by traversing mObservers, get all ObserverWrapper (actually the previous LifecycleBoundObserver), and notify the observer The user calls the considerNotify method, which is the concrete implementation of the notification.

Take a look at the considerNotify method, which handles each observer to determine whether it is necessary to distribute and complete the distribution event:

LiveData.java:

private void considerNotify(ObserverWrapper observer) {

    //If the observer is not active, the distribution will be given up. The logic for the observer to be active is between STARTED and RESUMED

    if (!observer.mActive) {

        return;

    }

    if (!observer.shouldBeActive()) {

        observer.activeStateChanged(false);

        return;

    }

    //If the observer has already accepted the data, it will no longer distribute

    if (observer.mLastVersion >= mVersion) {

        return;

    }

    observer.mLastVersion = mVersion; //Record the distribution version of the observer

    observer.mObserver.onChanged((T) mData); //call the observer's onChanged event to complete the distribution

}

First, a judgment is made, whether the observer object passed in from the outside is in an active state, and if it is not in an active state, return this method directly. It can be seen from here that even if the data is updated, if the interface is not active, it will not respond to the data update.

Then there is the mVersion variable and the mLastVersion variable. If the observer.mLastVersion variable is smaller than mVersion, the value of mVersion will be assigned to the observer.mLastVersion variable. In this case, the data is new data, and the onChanged method of the observer can be executed to convert mData Called back out.

③ postValue method

LiveData.java:

protected void postValue(T value) {

    boolean postTask;

    synchronized (mDataLock) {

        postTask = mPendingData == NOT_SET;

        mPendingData = value;

    }

    if (!postTask) {

        return;

    }

    ArchTaskExecutor.getInstance().postToMai nThread(mPostValueRunnable);

}

In the postValue method, the value passed in from the outside will be assigned to mPendingData. At the end of the method, the postToMainThread method, postToMainThread, should be seen from the name of this method, which is to publish this runnable to the main thread for processing, and enter postToMainThread for details have a look:

ArchTaskExecutor.java:

@Override

public void postToMainThread(Runnable runnable) {

    mDelegate.postToMainThread(runnable);

}

It calls the postToMainThread method of mDelegate, mDelegate is actually the DefaultTaskExecutor class, then enter the postToMainThread of this class to see:

DefaultTaskExecutor.java:

@Override

public void postToMainThread(Runnable runnable) {

    if (mMainHandler == null) {

        synchronized (mLock) {

            if (mMainHandler == null) {

                mMainHandler = new Handler(Looper.getMainLooper());

            }

        }

    }

    mMainHandler.post(runnable);

}

In this method, first determine whether the mMainHandler object is null, and if it is null, create one. Note that it is created through the Looper.getMainLooper() parameter, indicating that this mMainHandler is the handler object of the main thread.

Finally, call the mMainHandler.post(runnable) method, and hand over the runnable object to the handler of the main thread for processing.

So what does the runnable execute?

Go back to the postValue method and find that runnable is mPostValueRunnable. See how mPostValueRunnable is defined:

LiveData.java:

private final Runnable mPostValueRunnable = new Runnable() {

    @Override

    public void run() {

        Object newValue;

        synchronized (mDataLock) {

            newValue = mPendingData;

            mPendingData = NOT_SET;

        }

        setValue((T) newValue);

    }

};

See its run method, first define a newValue variable, and then add a lock to assign newValue to the mPendingData variable. mPendingData is assigned in the third line of the postValue method, which has been seen before, and then set mPendingData to NOT_SET. Finally call the setValue method.

Therefore, the postValue method actually cuts the thread to the main thread to process the setValue method.

 

4. LiveData's response to the life cycle

For example: There is an interface that monitors a LiveData object. When the interface is inactive, if the LiveData data is updated, the interface will not immediately respond to the change of LiveData (because the interface is inactive). When the interface returns to the active state, the newly updated LiveData data will respond immediately.

Then why when the interface returns to the active state (for example, the activity returns from the onPause state to the onResume state), the LiveData data just updated will respond immediately?

This is because in the observe method of LiveData, through the owner.getLifecycle().addObserver(wrapper) method, the life cycle of the wrapper and the owner will be bound, that is to say, the life cycle of the owner changes, and the wrapper can be in onStateChanged. method is monitored.

Assuming that the owner is an Activity, then when the Activity is in an inactive state, if the setValue operation is performed on LiveData, it will be returned directly at the beginning of the considerNotify method, and the observer.mObserver.onChanged method will not be reached, and it will not be received. The data change of LiveData.

When the Activity returns to the active state, the onStateChanged method of the wrapper (ie LifecycleBoundObserver) will be called back:

LiveData.LifecycleBoundObserver类:

@Override

public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {

    if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {

        removeObserver(mObserver);

        return;

    }

    activeStateChanged(shouldBeActive());

}

If the Activity is in an active state, it will execute the activeStateChanged method in onStateChanged:

void activeStateChanged(boolean newActive){

    if (newActive == mActive) {

        return;

    }

    mActive = newActive;

    boolean wasInactive = LiveData.this.mActiveCount == 0;

    LiveData.this.mActiveCount += mActive ? 1 : -1;

    if (wasInactive && mActive) {

        onActive();

    }

    if (LiveData.this.mActiveCount == 0 && !mActive) {

        onInactive();

    }

    //If the active state is processed, execute the dispatchingValue(this) method

    if (mActive) {

        dispatchingValue(this);

    }

}

First of all, you can see that mActive is assigned a value here. Then see the last three lines, judge that if it is active, execute the dispatchingValue(this) method, then return to the process of setValue at this time, that is, after the Activity life cycle becomes active, LiveData can be triggered again The value of is updated.

 

5. Automatic unbinding of LiveData

LiveData can greatly reduce the possibility of memory leaks because it can automatically unbind the components it is bound to, so how does this work? Still see the inner class LifecycleBoundObserver:

LiveData.java:

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {

    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {

        super(observer);

        mOwner = owner;

    }

    @Override

    public void onStateChanged( LifecycleOwner source, Lifecycle.Event event){

        if (mOwner.getLifecycle().getCurrentSta te() == DESTROYED) {

            removeObserver(mObserver);

            return;

        }

        activeStateChanged(shouldBeActive());

    }

    @Override

    boolean isAttachedTo(LifecycleOwner owner) {

        return mOwner == owner;

    }

    @Override

    void detachObserver() {

        mOwner.getLifecycle().removeObserver( this);

    }

}

In the onStateChanged method, if it is detected that the owner's life is in DESTROYED, the removeObserver method in the LiveData class will be executed, and its implementation is as follows:

@MainThread

public void removeObserver(final Observer<? super T> observer) {

    assertMainThread("removeObserver");

    ObserverWrapper removed = mObservers.remove(observer);

    if (removed == null) {

        return;

    }

    //Execute the detachObserver method of LifecycleBoundObserver

    removed.detachObserver();

    //Execute the activeStateChanged method of ObserverWrapper

    removed.activeStateChanged(false);

}

The main process of the removeObserver method is divided into three steps:

① First remove the current observer object from the mObservers collection.

② Execute removed.detachObserver(), this line of code will actually execute the detachObserver method of LifecycleBoundObserver, here is where mOwner and mObserver are really unbound.

③ Execute removed.activeStateChanged(false), this line of code will actually execute the activeStateChanged method of ObserverWrapper, and pass in false.

 

6. mData of LiveData

In the setValue method, the value will be assigned to the mData member variable of LiveData, look at this mData.

private volatile Object mData = NOT_SET;

It can be seen that it is modified by volatile. In java concurrency, volatile is used to ensure the visibility of variables, and prevent data inconsistency from being modified by multiple threads on the same variable under concurrency.

It can be seen from this that mData in LiveData is thread-safe.

 

Changes to data in LiveData:

①Transformations.map()

If you want to make changes to the values ​​stored in the LiveData object before it is dispatched to observers, you can use Transformations.map().

public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        MutableLiveData<String> mutableLiveData = new MutableLiveData<>();

        mutableLiveData.observe(this, new Observer<String>() {

            @Override

            public void onChanged(final String s) {

                Log.d(TAG, "onChanged1:"+s);

            }

        });

        LiveData transformedLiveData =Transformations.map(mutableLiveData, new Function<String, Object>() {

            @Override

            public Object apply(String name) {

               return name + "+Android Developer";

            }

        });

        transformedLiveData.observe(this, new Observer() {

            @Override

            public void onChanged(Object o) {

                Log.d(TAG, "onChanged2:"+o.toString());

            }

        });

        mutableLiveData.postValue("Android's most powerful language");

    }

}

Through Transformations.map(), a string is added on the basis of mutableLiveData.

Print result:

D/MainActivity: onChanged1: Android's most powerful language

D/MainActivity: onChanged2: Android's most powerful language + Android developers

②Transformations.switchMap()

If you want to manually control and monitor the data changes of one of them, and switch the monitoring at any time as needed, you can use Transformations.switchMap(), which is similar to Transformations.map(), except that switchMap() must return a LiveData object.

public class MainActivity extends AppCompatActivity {

    MutableLiveData<String> mutableLiveData1;

    MutableLiveData<String> mutableLiveData2;

    MutableLiveData<Boolean> liveDataSwitch;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mutableLiveData1 = new MutableLiveData<>();

        mutableLiveData2 = new MutableLiveData<>();

        liveDataSwitch = new MutableLiveData<Boolean>();

        LiveData transformedLiveData= Transformations.switchMap(liveDataSwitch, new Function<Boolean, LiveData<String>>() {

            @Override

            public LiveData<String> apply(Boolean input) {

                if (input) {

                    return mutableLiveData1;

                } else {

                    return mutableLiveData2;

               }

            }

        });

        transformedLiveData.observe(this, new Observer<String>() {

            @Override

            public void onChanged(final String s) {

                Log.d(TAG, "onChanged:" + s);

            }

        });

        liveDataSwitch.postValue(false);//2

        mutableLiveData1.postValue("Android developer");

        mutableLiveData2.postValue("Android language invincible");

    }

}

Create a new MutableLiveData<Boolean>() to control switching and assign it to liveDataSwitch. When the value of liveDataSwitch is true, it returns mutableLiveData1, otherwise it returns mutableLiveData2, which achieves the purpose of switching monitoring.

If liveDataSwitch.postValue(false), print Android language invincibility;

If liveDataSwitch.postValue(true), print Android developer;

③Merge multiple LiveData data sources

MediatorLiveData inherits from mutableLiveData, it can gather multiple LiveData data sources, and achieve the purpose of a component monitoring multiple LiveData data changes.

public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        MutableLiveData<String> mutableLiveData1 = new MutableLiveData<>();

        MutableLiveData<String> mutableLiveData2 = new MutableLiveData<>();

        MediatorLiveData liveDataMerger = new MediatorLiveData<String>();

        liveDataMerger.addSource( mutableLiveData1, new Observer() {

            @Override

            public void onChanged(Object o) {

                Log.d(TAG, "onChanged1:"+o.toString());

            }

        });

        liveDataMerger.addSource( mutableLiveData2, new Observer() {

            @Override

            public void onChanged(Object o) {

                Log.d(TAG, "onChanged2:"+o.toString());

            }

        });

        liveDataMerger.observe(this, new Observer() {

            @Override

            public void onChanged(Object o) {

                Log.d(TAG, "onChanged:"+o.toString());

            }

        });

        mutableLiveData1.postValue("Android Advanced Light");

    }

}

Merge the two MutableLiveData together through the addSource of MediatorLiveData, so that when any MutableLiveData data changes, MediatorLiveData can perceive it.

 

7.LiveData cross-component communication

LiveData can notify components of data changes in a timely manner, and can also automatically unbind, but the use of LiveData is more than that.

I don’t know if you have used EventBus before. This thing can replace Android’s traditional Intent, Handler, Broadcast or interface callback, and transfer data and execute methods between Fragment, Activity, and Service threads. The biggest feature of EventBus is simplicity and decoupling. Its frame idea is the publication and subscription of messages. It simplifies Android event delivery by decoupling publishers and subscribers.

In fact, a framework similar to EventBus can also be implemented through LiveData, and there is no need to manually unbind after use, which is more convenient and elegant. So, how to achieve it?

First of all, it is conceivable that a LiveData can be set as static, so that different Activities or Fragments can access this variable, so it is not a problem to monitor the value change of this variable. This method can indeed realize LiveData cross-Activity/Fragment communication.

For example: Suppose there are two interfaces, namely Activity1 and Activity2, Activity1 sends data through LiveData, and then receives data in Activity2. Then you can create a new MyStaticLiveData class to save public LiveData:

class MyStaticLiveData {

    companion object {

        val liveData = MutableLiveData<String>()

    }

}

Then send data in Activity1, and there is a button to jump to Activity2:

class Activity1 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView( R.layout.activity_live_data)

        MyStaticLiveData.liveData.value = "data from LiveDataActivity."

    }

    fun toLiveData2Activity(view: View) {

        startActivity(Intent(this, Activity2::class.java))

    }

}

In Activity2, the public LiveData and Activity2 are bound and monitored, so that the data sent by Activity1 can be received, and there is no need to unbind LiveData in onDestroy, because it will be automatically unbound.

class Activity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView( R.layout.activity_live_data2)

        MyStaticLiveData.liveData.observe(this, {

            tv2.text = it

        })

    }

}

It seems that the function of EventBus sticky event is indeed realized, and the usage is more concise.

But its shortcomings are also obvious. It is not elegant enough. Moreover, whenever you want to create a new LiveData, you need to manually create it in MyStaticLiveData. In addition, there is no one thing to manage these LiveData uniformly, and each LiveData They are all static, so it is not recommended to write in this way during development. This is only for demonstration, and it is not recommended to be used in actual development scenarios.

In addition, during the development process, sticky events are not needed in most cases. In more scenarios, we hope that the listener can start receiving the sent messages after registering for monitoring, and will not receive the messages sent before the registration.

Sticky events are when the LiveData changes before the observer starts observing, and when the observer starts observing. Observers will still be notified of the change, which is a sticky event.

 

8. Data backfeeding

For the example just demonstrated, there is another more common name in the industry, data backflow. In a word, the value of LiveData is set first, and then the listener starts to monitor LiveData. At this time, the value of LiveData will be immediately called back to the listener.

Data backflow, that is, after setValue, observe will consume the value of this set multiple times. For example, the data obtained during the second observe is the old data of the first time. This will have unpredictable consequences.

The reason for data backflow has to be analyzed from the perspective of source code, first look at the setValue method:

LiveData.java:

static final int START_VERSION = -1;

private int mVersion = START_VERSION;

@MainThread

protected void setValue(T value) {

    assertMainThread("setValue");

    mVersion++;

    mData = value;

    dispatchingValue(null);

}

Note that the mVersion here is auto-incremented in setValue. Also, the value of mVersion defaults to -1. So when setValue is called for the first time, the value of mVersion in LiveData is equal to 0.

When calling LiveData for observe, observe will eventually go to the considerNotify method:

LiveData.java:

private void considerNotify(ObserverWrapper observer) {

    if (observer.mLastVersion >= mVersion) {

        return;

    }

    observer.mLastVersion = mVersion;

    observer.mObserver.onChanged((T) mData);

}

private abstract class ObserverWrapper {

    ......

    int mLastVersion = START_VERSION;

    ......

}

It can be seen that when the mLastVersion of the observer is smaller than mVersion, the previous data will be called back to the listener. In addition, the default value of mLastVersion in observer is also -1, so in considerNotify, it is obvious that mLastVersion is smaller than mVersion (-1 < 0), so the onChanged method will be executed to call back the data to the listener.

Note: When the data of the observed object changes, how to determine that the change needs to be notified to the observer? In the livedata source code, there are two version numbers used for this control. The first version number is the version number of livedata, which belongs to the observer, and the second version number is that each observer of the livedata has a lastVersion, and the values ​​of these two version numbers will be synchronized after each successful distribution. Update the lastVersion of each observer to the livedata version number.

Example of data backflow: For example, when the screen is rotated, the Activity will be rebuilt. If the Activity is abnormally destroyed and then rebuilt, the ViewModel will save the data before the destruction, and then restore the data after the Activity is rebuilt. Therefore, the mVersion in the LiveData member variable will be restored to the value before the rebuild. value. However, after the Activity is rebuilt, the observe() method of LiveData will be called, and a new instance will be created inside the method, and the mLastVersion will be restored to the initial value. Due to the characteristics of LiveData itself, when the life cycle of the Activity changes from inactive to active, LiveData will trigger event distribution, resulting in data backflow after the screen is rotated.

Note: The ViewModel will not be destroyed when the Activity screen is rotating, so that the ViewModel can restore the state as long as it holds all the data of the Activity.

9. Data backfilling solution

①Reflection modify mVersion.

According to the previous analysis, it can be known that the key for LiveData to judge whether the event is distributed is in the considerNotify method.

private void considerNotify(ObserverWrapper observer) {

    if (!observer.mActive) {

        return;

    }

    if (!observer.shouldBeActive()) {

        observer.activeStateChanged(false);

        return;

    }

    if (observer.mLastVersion >= mVersion) {

        return;

    }

    observer.mLastVersion = mVersion;

    //noinspection unchecked

    observer.mObserver.onChanged((T) mData);

}

Every time setValue or postValue, mVersion will be +1, as long as mLastVersion>=mVersion, it proves that there was setValue or postValue before. Now we want to prevent the setValue method before calling the observer from being distributed, we only need to change mLastVersion = mVersion at a node before calling the observer.

Through the source code discovery, you can find the mObservers object and the current mVersion in the observer through reflection, and then you can assign mVersion to mLastVersion here.

private void hook(@NonNull Observer observer) throws Exception {

    //get wrapper's version

    Class classLiveData = LiveData.class;

    Field fieldObservers = classLiveData.getDeclaredField("mObservers");

    fieldObservers.setAccessible(true);

    Object objectObservers = fieldObservers.get(this);

    Class> classObservers = objectObservers.getClass();

    Method methodGet = classObservers.getDeclaredMethod("get", Object.class);

    methodGet.setAccessible(true);

    Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);

    Object objectWrapper = null;

    if (objectWrapperEntry instanceof Map.Entry) {

        objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();

    }

    if (objectWrapper == null) {

        throw new NullPointerException( "Wrapper can not be bull!");

    }

    Class> classObserverWrapper = objectWrapper.getClass().getSuperclass();

    Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");

    fieldLastVersion.setAccessible(true);

    //get livedata's version

    Field fieldVersion = classLiveData.getDeclaredField("mVersion");

    fieldVersion.setAccessible(true);

    Object objectVersion = fieldVersion.get(this);

    //set wrapper's version

    fieldLastVersion.set(objectWrapper, objectVersion);

}

Then rewrite LiveData and put this hook method in the observe method.

In this way, when using this custom LiveData, you will find that the method of setting Value first and then observing is no longer feasible. This is the so-called non-stickiness.

② Use SingleLiveEvent

SingleLiveEvent, as the name implies, is a LiveData that will only send an update once. It can only respond to one onChanged operation when observe#LiveData.

Its code is implemented as follows:

class SingleLiveEvent<T> : MutableLiveData<T>() { 

    private val mPending = AtomicBoolean(false) 

    @MainThread

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {

        if (hasActiveObservers()) {

            Log.w("SingleLiveEvent", "Multiple observers registered but only one will be notified of changes.")

        }

        super.observe(owner, { t ->

            if (mPending.compareAndSet(true, false)) {

                observer.onChanged(t)

            }

        })

    } 

    @MainThread

    override fun setValue(t: T?) {

        mPending.set(true)

        super.setValue(t)

    }

    @MainThread

    fun call() {

        value = null

    }

}

The idea of ​​SingleLiveEvent is that every time onChanged is triggered, a Boolean value mPending will be used to judge whether the last setValue event has been consumed. If it has been consumed, the consumption will not be passed on.

Step 1: SingleLiveEvent uses AtomicBoolean (default is false) for assignment, and changes the value of AtomicBoolean (set(true)) when LiveData performs setValue.

Step 2: Use the AtomicBoolean.compareAndSet(true,false) method to first judge (the value of AtomicBoolean at this time is true) and compare it with the except value (the first parameter) set by compareAndSet, and compare the second parameter because it is equal Set the value of AtomicBoolean to false and return true), here is actually using the atomicity of AtomicBoolean to perform a process of comparison and reassignment.

Step 3: When entering the page again, although the LiveData value has not changed, the observer method is still triggered. Since the AtomicBoolean is already false, but the except value is true, it will not continue to trigger the onChanged(T) method by judging with if. That is, the onChanged(T) method is only responded to once when setValue is set.

In fact, SingleLiveEvent does not solve the 'sticky' problem. The scenario it is applicable to is that after one setValue, there are multiple observations, but only one observation is consumed. However, the problem with SingleLiveEvent is that it is limited to one observer. If more than one is inadvertently added, only one will be called, and there is no guarantee which one.

③UnPeekLiveData

This is a solution to such problems open sourced by KunMinX.

public class ProtectedUnPeekLiveData extends LiveData {

    protected boolean isAllowNullValue;

    private final HashMap observers = new HashMap();

    public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer super T> observer) {

        LifecycleOwner owner = activity;

        Integer storeId = System.identityHashCode(observer);//The source code here is activity.getViewModelStore(), which is to ensure the "unique trusted source" in the same ViewModel environment

        observe(storeId, owner, observer);

    }

    private void observe(@NonNull Integer storeId,@NonNull LifecycleOwner owner, @NonNull Observer super T> observer) {

        if (observers.get(storeId) == null) {

            observers.put(storeId, true);

        }

        super.observe(owner, t -> {

            if (!observers.get(storeId)) {

                observers.put(storeId, true);

                if (t != null || isAllowNullValue) {

                    observer.onChanged(t);

                }

            }

        });

    }

    @Override

    protected void setValue(T value) {

        if (value != null || isAllowNullValue) {

            for (Map.Entry entry : observers.entrySet()) {

                entry.setValue(false);

            }

            super.setValue(value);

        }

    }

    protected void clear() {

        super.setValue(null);

    }

}

The idea is also very clear, carrying a Boolean value for each incoming observer object as a switch whether it can enter the observe method. Whenever a new observer is stored, the switch is turned off by default.

After each setValue, turn on all Observer switches to allow all observes to execute.

At the same time, after the method enters, close the currently executing observer switch, that is, it cannot be executed for the second time, unless you reset Value.

Through this mechanism, it is possible to realize the non-sticky state of LiveData without reflection technology.

 

 

Guess you like

Origin blog.csdn.net/zenmela2011/article/details/130068181