当前位置:网站首页>Livedata interview question bank and answers -- 7 consecutive questions in livedata interview~

Livedata interview question bank and answers -- 7 consecutive questions in livedata interview~

2022-07-05 10:11:00 A bird carved in the desert

author : Tang Zixuan
link :
https://juejin.cn/post/7085037365101592612

Introduction

LiveData Is aware of the life cycle , Observable , Sticky , Data holder .LiveData Used to “ Data driven ” Mode update interface .

Put it another way :LiveData Cache the latest data and pass it to the active component .

For a detailed explanation of data driving, you can click How do I write business code more and more complex | MVP - MVVM - Clean Architecture.

This one is just LiveData Make a summary of the interview questions 、 analysis 、 answer .

1. LiveData How to perceive changes in the life cycle ?

Summarize first , reanalysis :

  • Jetpack Introduced Lifecycle, Make it easy for any component to perceive the changes in the interface life cycle . Just need to achieve LifecycleEventObserver Interface and register it with the lifecycle object .
  • LiveData The data observer is internally packaged as another object ( Realized LifecycleEventObserver Interface ), It has both data observation ability and life cycle observation ability .

In conventional observer mode , As long as the observer changes , Will inform all observers unconditionally . such as java.util.Observable

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!hasChanged())
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        //  Unconditionally traverse all observers and notify 
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
}
//  The observer 
public interface Observer {
    void update(Observable o, Object arg);
}

LiveData Conditions are added to the conventional observer mode , If the life cycle is not up to standard , Even if the data changes, the observer is not informed . How this is achieved ?

Life cycle

Life cycle is the general name of each state of an object from construction to extinction .

such as Activity The life cycle of is expressed by the following function in turn :

onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()

To observe the life cycle, you have to inherit Activity Override these methods , Trying to distribute life cycle changes to other components is cumbersome .

therefore Jetpack Introduced Lifecycle, So that any component can easily perceive the changes in the life cycle :

public abstract class Lifecycle {AtomicReference<>();
    //  Add life cycle watcher 
    public abstract void addObserver(LifecycleObserver observer);
    //  Remove lifecycle watcher 
    public abstract void removeObserver(LifecycleObserver observer);
    //  Get the current lifecycle state 
    public abstract State getCurrentState();
    //  Life cycle events 
    public enum Event {
        ON_CREATE,
        ON_START,
        ON_RESUME,
        ON_PAUSE,
        ON_STOP,
        ON_DESTROY,
        ON_ANY;
    }
    //  Life Cycle States 
    public enum State {
        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;
    }
    //  Judge that at least a certain lifecycle state has been reached 
    public boolean isAtLeast(State state) {
        return compareTo(state) >= 0;
    }
}

Lifecycle That is, the class corresponding to the life cycle , Provides the addition / How to remove the life cycle observer , It also defines the States and corresponding events of the whole life cycle .

Lifecycle states are in order , Corresponding to the small to large int value .

Lifecycle owner

There are already objects that describe the life cycle , How to get this object requires a unified interface ( Otherwise, directly in Activity perhaps Fragment Add a new method in ?), This interface is called LifecycleOwner

public interface LifecycleOwner {
    Lifecycle getLifecycle();
}

Activity and Fragment All implement this interface .

Just get it LifecycleOwner, Can get Lifecycle, Then you can register a lifecycle observer .

Life cycle & Data observer

The lifecycle observer is an interface :

//  Life cycle observer ( Empty interface , Used to characterize a type )
public interface LifecycleObserver {}
//  Life cycle event observer 
public interface LifecycleEventObserver extends LifecycleObserver {
    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
}

To observe the life cycle, just realize LifecycleEventObserver Interface , And register with LifeCycle that will do .

In addition to life cycle observers ,LiveData There is another in the scene Data observer

//  Data observer 
public interface Observer<T> {
    //  Callback when data changes 
    void onChanged(T t);
}

Data observer Hui He Lifecycle owner Binding :

public abstract class LiveData<T> {
    //  Data observer container 
    private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();
            
    public void observe(
        LifecycleOwner owner, //  The bound lifecycle owner 
        Observer<? super T> observer //  Data observer 
    ) {
        ...
        //  Wrap the data observer into  LifecycleBoundObserver
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        //  Store what the observer sees  map  structure 
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        ...
        //  Register life cycle watcher .
        owner.getLifecycle().addObserver(wrapper);
    }
}

Observing LiveData when , Two parameters need to be passed in , Lifecycle owners and data observers . These two objects pass through LifecycleBoundObserver The packages are bound together :

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    //  Hold the life cycle owner 
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }
    //  Lifecycle change callback 
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { 
        ...
        activeStateChanged(shouldBeActive())
        ...
    }
}

//  Observer packaging type 
private abstract class ObserverWrapper {
    //  Observers with raw data 
    final Observer<? super T> mObserver;
    //  Inject data into the observer 
    ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}
    //  Try to distribute the latest values to the current data observer 
    void activeStateChanged(boolean newActive) {...}
    ...
}

LifecycleBoundObserver Realized LifecycleEventObserver Interface , And it is registered with the bound lifecycle object , So it has the ability to perceive the life cycle . At the same time, it also holds the data observer , So it also has the ability to observe data .

2. LiveData How to avoid memory leakage ?

Summarize first , reanalysis :

  • LiveData The data observer is usually an anonymous inner class , It holds references to the interface , May cause memory leak .
  • LiveData The data observer will be encapsulated internally , Make it life cycle aware . When the lifecycle state is DESTROYED when , Automatically remove the observer .

The memory leak is due to Long life objects hold short life objects , Prevents it from being recycled .

Observe LiveData The code of data is usually written like this :

class LiveDataActivity : AppCompatActivity() {
    private val viewModel by lazy {
        ViewModelProviders.of([email protected]).get(MyViewModel::class.java)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.livedata.observe([email protected]) {
            //  Observe  LiveData  Data update ( Anonymous inner class )
        }
    }
}

Observer As an anonymous inner class of the interface , It will hold references to the interface , meanwhile Observer By LiveData hold ,LivData By ViewModel hold , and ViewModel Life cycle ratio of Activity Long .( Why is it longer than it , You can click on the here ).

The final holding chain is as follows :NonConfigurationInstances hold ViewModelStore hold ViewModel hold LiveData hold Observer hold Activity.

So it has to be removed at the end of the interface life cycle Observer, This matter ,LiveData Did it for us .

stay LiveData Inside Observer It's going to be packaged as LifecycleBoundObserver

class LifecycleBoundObserver extends ObserverWrapper 
    implements LifecycleEventObserver {
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }

    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        //  Get the current lifecycle 
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        //  If the life cycle is  DESTROYED  Remove the observer and return the data 
        if (currentState == DESTROYED) {
            removeObserver(mObserver);
            return
        }
        ...
    }
    ...
}

3. LiveData Is it sticky ? if , How does it do it ?

Summarize first , reanalysis :

  • LiveData The value of is stored in an internal field , Until an updated value overrides , So the value is persistent .
  • In two scenarios LiveData The stored values are distributed to the observer . First, the value is updated , All observers will be traversed and distributed . Second, new observers or changes in the life cycle of observers ( At least for STARTED), Only values are distributed to a single observer .
  • LiveData The observer will maintain a “ The version number of the value ”, Used to determine whether the last distributed value is the latest value . The initial value of this value is -1, Each update LiveData Value will increase the version number .
  • LiveData Values are not unconditionally distributed to observers , There are three hurdles before distribution :1. Whether the data observer is active .2. Whether the lifecycle component bound by the data observer is active .3. Whether the observer's data is the latest version .
  • “ The new observer ” By “ Old value ” The phenomenon of notification is called “ viscosity ”. Because the version number of the new observer is always less than the latest version number , And adding observers will trigger a distribution of old values .

If you put sticky Translate into “ lasting ”, I'll understand better . Data is persistent , Means it's not fleeting , It won't disappear because it's consumed , It will always be there . And when a new observer is registered , Persistent data will distribute the latest values to it .

“ Persistent data ” How to do it ?

Obviously saved . To update LiveData The method of data is to find clues for the entry point :

public abstract class LiveData<T> {
    //  The field in which data is stored 
    private volatile Object mData;
    //  Value version number 
    private int mVersion;
    //  Update value 
    protected void setValue(T value) {
        assertMainThread("setValue");
        //  The version number increases automatically 
        mVersion++;
        //  Stored value 
        mData = value;
        //  Distribution value 
        dispatchingValue(null);
    }
}

setValue() Is an update LiveData Value is a method that must be called , Even through postValue() Update value , Will eventually take this method .

LiveData Hold a version number field , Used to identify “ The version of the value ”, Just like the software version number , This number is used to judge “ Whether the current value is the latest ”, If the version number is less than the latest version number , Indicates that the current value needs to be updated .

LiveData Use one Object Field mData Store “ value ”. So this value will always exist , Until overwritten by the updated value .

LiveData Distributing values is notifying the data observer :

public abstract class LiveData<T> {
    //  An observer who holds a set of data in key value pairs 
    private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();
    void dispatchingValue(ObserverWrapper initiator) {
            ...
            //  Assign distribution to a single data observer 
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } 
            //  Traverse all data observers and distribute values 
            else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                }
            }
            ...
    }
    
    //  Really distribute value 
    private void considerNotify(ObserverWrapper observer) {
        // 1.  If the observer is not active, do not distribute it 
        if (!observer.mActive) {
            return;
        }
        // 2.  Judge again whether it is active according to the life cycle of the observer binding , If not active, do not distribute it 
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        // 3.  If the value is already the latest version , Do not distribute 
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //  Update the latest version number of the observer 
        observer.mLastVersion = mVersion;
        //  Really inform the observer 
        observer.mObserver.onChanged((T) mData);
    }

}

There are two cases of distributing values :“ Distribute to a single observer ” and “ Distribute to all observers ”. When LiveData When the value is updated , To be distributed to all observers .

All observers are present in one Map In structure , The way of distribution is through traversal Map And call... One by one considerNotify(). In this method, three barriers need to be crossed , To really distribute values to data observers , Namely :

  1. Whether the data observer is active .
  2. Whether the lifecycle component bound by the data observer is active .
  3. Whether the observer's data is the latest version .

After crossing three ridges , The latest version number will be stored in the observer's mLastVersion Field , That is, the version number is saved in LiveData.mVersion, A copy will also be saved in each observer mLastVersion, Finally, the previously temporarily stored mData The value of is distributed to the data observer .

Each data observer is bound to a component's lifecycle object ( See Section 1 ), When the component life cycle changes , Will try to distribute the latest value to the data observer .

Every data observer is packaged ( See Section 1 ), The packing type is ObserverWrapper

//  Raw data observer 
public interface Observer<T> {
    void onChanged(T t);
}

//  Observer packaging type 
private abstract class ObserverWrapper {
    //  Observers with raw data 
    final Observer<? super T> mObserver;
    //  Whether the current observer is active 
    boolean mActive;
    //  Current observer's latest value version number , The initial value is  -1
    int mLastVersion = START_VERSION;
    //  Inject the original observer 
    ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}
    //  When the life cycle of the component bound by the data observer changes , Try to distribute the latest value to the current observer 
    void activeStateChanged(boolean newActive) {
        //  If the observer remains active , The value is not distributed 
        if (newActive == mActive) {
            return;
        }
        //  Update active status 
        mActive = newActive;
        //  If active , Then the latest value is distributed to the current observer 
        if (mActive) {
            dispatchingValue(this);
        }
    }
    //  Is it active , For subclasses to override 
    abstract boolean shouldBeActive();
}

The observer's packaging type holds an original observer in a combined way , On this basis, the concepts of active state and version number are extended .

The observer wrapper type is abstract , Activity is defined by subclasses :

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }

    //  When the lifecycle component bound to the observer is at least STARTED when , Indicates that the observer is active 
    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged( LifecycleOwner source, Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        //  When the lifecycle state changes , Then try to distribute the latest value to the data observer 
        while (prevState != currentState) {
            prevState = currentState;
            //  Call the superclass method , distributed 
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }
}

To sum up ,LiveData There are two opportunities to inform the observer , There are two ways to distribute values :

  1. When the value is updated , Traverse all observers and distribute the latest values to them .
  2. When the life cycle of the component bound to the observer changes , Distribute the latest values to the specified observer .

Suppose such a scenario :LiveData The value of is updated once , Then it was added a new data observer , The life cycle of the components bound to it has also changed ( Change to RESUMED), That is, the data is updated before adding observers , Will the updated value be distributed to the new observer ?

Meeting ! First , The updated value will be stored in mData Field .

secondly , A lifecycle change is triggered when an observer is added :

// androidx.lifecycle.LifecycleRegistry
public void addObserver(@NonNull LifecycleObserver observer) {
    State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
    ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
    ...
    //  Distribute life cycle events to new observers 
    statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
    ...
}

// LifecycleBoundObserver  Another layer 
static class ObserverWithState {
    State mState;
    GenericLifecycleObserver mLifecycleObserver;

    ObserverWithState(LifecycleObserver observer, State initialState) {
        mLifecycleObserver = Lifecycling.getCallback(observer);
        mState = initialState;
    }

    void dispatchEvent(LifecycleOwner owner, Event event) {
        State newState = getStateAfter(event);
        mState = min(mState, newState);
        //  Distribute lifecycle events to  LifecycleBoundObserver
        mLifecycleObserver.onStateChanged(owner, event);
        mState = newState;
    }
}

Last , This attempt is bound to cross three hurdles , Because the new observer version number is always less than LiveData Version number of (-1 < 0,LiveData.mVersion After a value update, it increases to 0).

such “ The new observer ” Will be “ Old value ” The phenomenon of is called notice of viscosity .

4. Sticky LiveData What's the problem ? How to solve ?

The shopping cart - Settlement scenario : Suppose there is a shopping cart interface , Click settlement to jump to the settlement interface , The settlement interface can be returned to the shopping cart interface . Both interfaces are Fragment.

The settlement interface and shopping cart interface are through share ViewModel Share the product list in a way :

class MyViewModel:ViewModel() {
    //  List of goods 
    val selectsListLiveData = MutableLiveData<List<String>>()
    //  Update product list 
    fun setSelectsList(goods:List<String>){
       selectsListLiveData.value = goods
    }
}

Here are two Fragment The interface relies on Activity

class StickyLiveDataActivity : AppCompatActivity() {
    //  use  DSL  Build views 
    private val contentView by lazy {
        ConstraintLayout {
            layout_id = "container"
            layout_width = match_parent
            layout_height = match_parent
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(contentView)
        //  Load shopping cart interface 
        supportFragmentManager.beginTransaction()
            .add("container".toLayoutId(), TrolleyFragment())
            .commit()
    }
}

It uses DSL The layout is constructed declaratively , For details, click Android performance optimization | Shorten the time it takes to build the layout 20 times ( Next )

The shopping cart page is as follows :

class TrolleyFragment : Fragment() {
    //  Get and host  Activity  The binding of  ViewModel
    private val myViewModel by lazy { 
        ViewModelProvider(requireActivity()).get(MyViewModel::class.java) 
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return ConstraintLayout {
            layout_width = match_parent
            layout_height = match_parent
            //  Add two items to the shopping cart 
            onClick = {
                myViewModel.setSelectsList(listOf("meet","water"))
            }

            TextView {
                layout_id = "balance"
                layout_width = wrap_content
                layout_height = wrap_content
                text = "balance"
                gravity = gravity_center
                //  Jump to settlement page 
                onClick = {
                    parentFragmentManager.beginTransaction()
                        .replace("container".toLayoutId(), BalanceFragment())
                        .addToBackStack("trolley")
                        .commit()
                }
            }
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  Observe the changes in the product list 
        myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods ->
            //  If the product list exceeds 2 Commodity , be  toast  Prompt full 
            goods.takeIf { it.size >= 2 }?.let {
                Toast.makeText(context," Shopping cart full ",Toast.LENGTH_LONG).show()
            }
        }
    }
}

stay onViewCreated() Observe the change of shopping cart , If the shopping cart exceeds 2 Commodity , be toast Tips .

The following is the settlement page :

class BalanceFragment:Fragment() {
    private val myViewModel by lazy { 
        ViewModelProvider(requireActivity()).get(MyViewModel::class.java) 
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return ConstraintLayout {
            layout_width = match_parent
            layout_height = match_parent
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  The way to get the shopping list in the settlement interface is also to observe the goods  LiveData
        myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {...}
    }
}

Run demo, Jump to the settlement interface , Click back to the shopping cart ,toast You will be prompted again that the shopping cart is full .

Because before jumping to the settlement page , Shopping cart list LiveData Has been updated . When the shopping cart page is re displayed ,onViewCreated() Will be executed again , Such a new observer is added , because LiveData It's sticky , So the last shopping cart list will be distributed to new observers , such toast The logic is executed again .

Solution 1 : Value with consumption record

//  One time value 
open class OneShotValue<out T>(private val value: T) {
    //  Whether the value is consumed 
    private var handled = false
    //  Get value , Returns... If the value is not processed , Otherwise, it returns empty 
    fun getValue(): T? {
        return if (handled) {
            null
        } else {
            handled = true
            value
        }
    }
    //  Get the last processed value 
    fun peekValue(): T = value
}

Put a layer on the outside of the value , Add a flag bit to identify whether it has been processed .

Use this method to reconstruct ViewModel:

class MyViewModel:ViewModel() {
    //  List of selected items 
    val selectsListLiveData = MutableLiveData<OneShotValue<List<String>>>()
    //  Update selected items 
    fun setSelectsList(goods:List<String>){
       selectsListLiveData.value = OneShotValue(goods)
    }
}

Observe the logic of the shopping cart and make changes :

class TrolleyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods ->
            goods.getValue()?.takeIf { it.size >= 2 }?.let {
                Toast.makeText(context," The shopping cart is full ",Toast.LENGTH_LONG).show()
            }
        }
    }
}

Repeat bullet toast The problem is solved , But it raises a new question : When the shopping cart is full, pop up toast when , The shopping cart list has been consumed , As a result, the settlement interface can no longer be consumed .

You can only use peekValue() To get the value that has been consumed :

class BalanceFragment:Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {
            val list = it.peekValue()//  Use  peekValue()  Get a list of shopping carts 
        }
    }
}

bug It's all solved . But don't you think it's a little twisted ?

use “ One time value ” encapsulation LiveData Value , To remove its stickiness . Using this scheme, we have to identify which observers need viscosity values , Which observers need non sticky Events . When there are many observers , It's hard to parry . If you write the logic that requires sticky and non sticky processing in an observer , Just GG, You have to create new observers to separate them .

Solution 2 : Observer with the latest version number

Three hurdles need to be crossed before notifying the observer ( See Section 3 ), One of them is the comparison of version numbers . If the new observer version number is less than the latest version number , It means that the observer is behind , You need to distribute the latest values to it .

LiveData Source code , The version number of a new observer is always -1.

//  Observer packaging type 
private abstract class ObserverWrapper {
    //  Current observer's latest value version number , The initial value is  -1
    int mLastVersion = START_VERSION;
    ...
}

If the version number of the new observer can be assigned by the latest version number , The comparison of the version number can't pass , New values cannot be distributed to new observers .

So it has to be modified by reflection mLastVersion Field .

In addition to its strong immersion , hold LiveData The stickiness is completely destroyed . But sometimes , We still want to use sticky ...

Solution three :SingleLiveEvent

This is a solution given by Google , Source code can be clicked here

public class SingleLiveEvent<T> extends MutableLiveData<T> {
    //  Sign a , Used to express whether the value is consumed 
    private final AtomicBoolean mPending = new AtomicBoolean(false);

    public void observe(LifecycleOwner owner, final Observer<T> observer) {
        //  Middle observer 
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                //  Only when the value is not consumed and out of date , To inform the downstream observer 
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    public void setValue(@Nullable T t) {
        //  When the value is updated , Set the flag to  true
        mPending.set(true);
        super.setValue(t);
    }

    public void call() {
        setValue(null);
    }
}

Set up a special LiveData, It's not sticky . It's through new “ Middle observer ”, Intercept upstream data changes , Then forward it to the downstream . After interception, you can usually do something , For example, add a marker bit mPending Judgment of whether it has been consumed , If it is consumed, it will not be forwarded to the downstream .

In data driven App Under the interface , There are two values :1. Non transient data 2. Transient data

demo Used to prompt “ Shopping cart full ” The data is “ Transient data ”, This data is one-off , Fleeting , You can spend it once and throw it away .

demo The list of items in the shopping cart is “ Non transient data ”, Its life cycle is a little longer than transient data , Both the shopping cart interface and the settlement interface should be able to be consumed repeatedly during their lifetime .

SingleLiveEvent The design of is based on this classification method of data , That is, transient data use SingleLiveEvent, Non transient data use conventional LiveData.

In this way, the solution of returning dust to earth is in line with the reality . take demo modified :

class MyViewModel : ViewModel() {
    //  Non transient shopping cart list  LiveData
    val selectsListLiveData = MutableLiveData<List<String>>()
    //  List of shopping carts  LiveData
    val singleListLiveData = SingleLiveEvent<List<String>>()
    //  Update shopping cart list , Update both transient and non transient data 
    fun setSelectsList(goods: List<String>) {
        selectsListLiveData.value = goods
        singleListLiveData.value = goods
    }
}

Make corresponding changes in the shopping cart interface :

class TrolleyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  Only observe the non transient shopping cart list 
        myViewModel.singleListLiveData.observe(viewLifecycleOwner) { goods ->
            goods.takeIf { it.size >= 2 }?.let {
                Toast.makeText(context,"full",Toast.LENGTH_LONG).show()
            }
        }
    }
}

But the scheme has limitations , if SingleLiveEvent Add multiple observers , When the first observer consumes the data , Other observers have no chance to consume . because mPending Shared by all observers .

The solution is simple , For each intermediate observer, it holds the flag bit of whether the data has been consumed :

open class LiveEvent<T> : MediatorLiveData<T>() {
    //  Hold multiple intermediate observers 
    private val observers = ArraySet<ObserverWrapper<in T>>()

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        observers.find { it.observer === observer }?.let { _ ->
            return
        }
        //  Build an intermediate observer 
        val wrapper = ObserverWrapper(observer)
        observers.add(wrapper)
        super.observe(owner, wrapper)
    }

    @MainThread
    override fun observeForever(observer: Observer<in T>) {
        observers.find { it.observer === observer }?.let { _ ->
            return
        }
        val wrapper = ObserverWrapper(observer)
        observers.add(wrapper)
        super.observeForever(wrapper)
    }

    @MainThread
    override fun removeObserver(observer: Observer<in T>) {
        if (observer is ObserverWrapper && observers.remove(observer)) {
            super.removeObserver(observer)
            return
        }
        val iterator = observers.iterator()
        while (iterator.hasNext()) {
            val wrapper = iterator.next()
            if (wrapper.observer == observer) {
                iterator.remove()
                super.removeObserver(wrapper)
                break
            }
        }
    }

    @MainThread
    override fun setValue(t: T?) {
        //  Inform all intermediate observers , There's new data 
        observers.forEach { it.newValue() }
        super.setValue(t)
    }

    //  Middle observer 
    private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {
        //  Mark whether the current observer has consumed data 
        private var pending = false

        override fun onChanged(t: T?) {
            //  Ensure that data is distributed only once to downstream observers 
            if (pending) {
                pending = false
                observer.onChanged(t)
            }
        }

        fun newValue() {
            pending = true
        }
    }
}

Solution 4 :Kotlin Flow

Limited to space and theme ( The theme is LiveData), Give the code directly ( Current practice is problematic ), About LiveData vs Flow For detailed analysis, click How to write business code more and more complex ?( Two )| Flow Replace LiveData Reconstruct the data link , more MVI

class MyViewModel : ViewModel() {
    //  Product list flow 
    val selectsListFlow = MutableSharedFlow<List<String>>()
    //  Update product list 
    fun setSelectsList(goods: List<String>) {
        viewModelScope.launch {
            selectsListFlow.emit(goods)
        }
    }
}

The shopping cart code is as follows :

class TrolleyFragment : Fragment() {
    private val myViewModel by lazy { 
        ViewModelProvider(requireActivity()).get(MyViewModel::class.java) 
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 1. First generate data 
        myViewModel.setSelectsList(listOf("food_meet", "food_water", "book_1"))
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 2. Then subscribe to the product list stream 
        lifecycleScope.launch {
            myViewModel.selectsListFlow.collect { goods ->
                goods.takeIf { it.size >= 2 }?.let {
                    Log.v("ttaylor", " The shopping cart is full ")
                }
            }
        }
    }
}

Data production before subscription , Subscription does not print log.

If so modified SharedFlow Build parameters for , You can make it sticky :

class MyViewModel : ViewModel() {
    val selectsListFlow = MutableSharedFlow<List<String>>(replay = 1)
}

replay = 1 Indicates that the latest data will be notified to new subscribers .

It just solves the problem of stickiness / The problem of convenient switching between non viscous , It does not solve the problem that multiple streams are still required . Take the next article to continue in-depth analysis .

5. Under what circumstances LiveData Data will be lost ?

Summarize first , reanalysis :

In the scenario of high-frequency data update LiveData.postValue() when , Can cause data loss . because “ Set the value ” and “ Distribution value ” It is executed separately , There is a delay between . Values are cached in variables first , Then throw a task of distributing values to the main thread . If you call again between these delays postValue(), The cached value in the variable is updated , Previous values are erased before they are distributed .

Here is LiveData.postValue() Source code :

public abstract class LiveData<T> {
    //  Temporary value field 
    volatile Object mPendingData = NOT_SET;
    private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                //  Get the temporary value synchronously 
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            //  Distribution value 
            setValue((T) newValue);
        }
    };
    
    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            //  Temporary value 
            mPendingData = value;
        }
        ...
        //  Throw... To the main thread  runnable
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
}

6. stay Fragment Use in LiveData What to pay attention to ?

Summarize first , reanalysis :

stay Fragment To observe LiveData When using viewLifecycleOwner instead of this. because Fragment and Among them View The life cycle is not completely consistent .LiveData The internal judgment life cycle is DESTROYED when , To remove the data observer . There is a situation , When Fragment When switching between , Replaced by Fragment Don't execute onDestroy(), When it shows again, it will subscribe again LiveData, So one more subscriber .

Or shopping - The scene of settlement : The shopping cart and the settlement page are two Fragment, Save item list to share ViewMode Of LiveData in , Both the shopping cart and the settlement page observe it , In addition to using it to list shopping lists on the settlement page , It can also be modified by changing the quantity of goods LiveData. When returning to the shopping cart page from the settlement page , The shopping cart interface has to refresh the quantity of goods .

The above scenario , If the shopping cart page looks LiveData When using this What's going to happen ?

//  Shopping cart interface 
class TrolleyFragment : Fragment() {
    private val myViewModel by lazy { 
        ViewModelProvider(requireActivity()).get(MyViewModel::class.java) 
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return ConstraintLayout {
            layout_width = match_parent
            layout_height = match_parent
            onClick = {
                parentFragmentManager.beginTransaction()
                    .replace("container".toLayoutId(), BalanceFragment())
                    .addToBackStack("trolley")//  Add shopping cart page to  back stack
                    .commit()
            }
        }
    }
    
    //  I have to add this comment , because  this  Will be red 
    @SuppressLint("FragmentLiveDataObserve")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  take  this  Pass on to... As the owner of the life cycle  LiveData
        myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> {
            override fun onChanged(t: List<String>?) {
                Log.v("ttaylor", " The quantity of goods has changed ")
            }
        })
    }
}

Write it like this this Will be red ,AndroidStudio It is not recommended to use it as a lifecycle owner , Have to add @SuppressLint("FragmentLiveDataObserve")

The code for modifying the commodity quantity in the settlement interface is as follows :

//  Settlement interface 
class BalanceFragment:Fragment() {
    private val myViewModel by lazy { 
        ViewModelProvider(requireActivity()).get(MyViewModel::class.java) 
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  Modify the commodity quantity in the simulated settlement interface 
        myViewModel.selectsListLiveData.value = listOf(" Number +1")
    }
}

When returning to the shopping cart from the settlement page ,“ The quantity of goods has changed ” It prints twice , If you go to the settlement page again and return to the shopping cart , Will print three times .

If you change into viewLifecycleOwner There would be no such trouble . Because use replace Replace Fragment when ,Fragment.onDestroyView() Will execute , namely Fragment Corresponding View The lifecycle state of will change to DESTROYED.

LiveData Internally, the life cycle will be DESTROYED The data observer removes ( See Section II for details ). When you return to the shopping cart again ,onViewCreated() Re execution ,LiveData A new observer will be added . Delete and add , The whole process LiveData There is always only one observer . Again because LiveData It's sticky , Even if the modified item quantity occurs before the observation , The latest quantity of goods will still be distributed to new observers .( See Section 3 )

But when using replace Replace Fragment And press it in back stack when ,Fragment.onDestroy() Not invoke ( Because it's stacked , Not destroyed ). This leads to Fragment The lifecycle state of does not change to DESTROYED, therefore LiveData Observers are not automatically removed . When returning to the shopping cart , Added a new observer . If you keep jumping between the shopping cart and the settlement page , Then the observer data will keep increasing .

Writing demo When I met a pit :

//  Shopping cart interface 
class TrolleyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //  To use intentionally  object  grammar 
        myViewModel.selectsListLiveData.observe(this, object : Observer<List<String>> {
            override fun onChanged(t: List<String>?) {
                Log.v("ttaylor", " The quantity of goods has changed ")
            }
        })
    }
}

In the build Observer At instance time , I used... On purpose Kotlin Of object grammar , In fact, it can be used lambda Write it more succinctly :

class TrolleyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        myViewModel.selectsListLiveData.observe(this) {
            Log.v("ttaylor", " The quantity of goods has changed ")
        }
    }
}

If you write like this , that bug It can't be reproduced ....

because java The compiler will take the same lambda Optimize into static , Can improve performance , You don't have to rebuild the inner class every time . But unfortunately LiveData When adding an observer, it will check whether it already exists , If it exists, it directly returns :

// `androidx.lifecycle.LiveData
public void observe( LifecycleOwner owner,  Observer<? super T> observer) {
    ...
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    //  call  map  Structure write operation , if  key  Already exists , Then return the corresponding  value
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    ...
    //  If it already exists, it will directly return 
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

In this case ,Fragment Repeated horizontal jumps between interfaces will not add observers .

7. How to transform LiveData Data and precautions ?

Summarize first , reanalysis :

androidx.lifecycle.Transformations Class provides three transformations LiveData Method of data , The most common is Transformations.map(), It USES MediatorLiveData As an intermediate consumer of data , And transfer the transformed data to the final consumer . It should be noted that , All data change operations take place in the main thread , The main thread may be blocked by time-consuming operations . The solution is to LiveData Asynchronization of data transformation operations , Such as through CoroutineLiveData.

Or shopping - The scene of settlement : The shopping cart and the settlement page are two Fragment, Save item list LiveData in , Both the shopping cart and the settlement page observe it . The settlement interface has a special function for discounted goods UI Exhibition .

At this point, you can list the products LiveData Make a transformation ( Filter ) Get a new list of discounted items :

class MyViewModel : ViewModel() {
    //  List of goods 
    val selectsListLiveData = MutableLiveData<List<String>>()
    //  List of discounted items 
    val foodListLiveData = Transformations.map(selectsListLiveData) { list ->
        list.filter { it.startsWith("discount") }
    }
}

Whenever the item list changes , The list of discounted items will be notified , Filter out new items and discount them . The list of discounted items is a new LiveData, Can be observed alone .

The filter list operation takes place in the main thread , If the business is slightly complex , If the data transformation operation is time-consuming , May block the main thread .

How to integrate LiveData Transform data asynchronization ?

LiveData Of Kotlin An extension package is provided that will LiveData The product of the combination with the synergetic process :

class MyViewModel : ViewModel() {
    //  List of goods 
    val selectsListLiveData = MutableLiveData<List<String>>()
    //  Get the list of discounted products asynchronously 
    val asyncLiveData = selectsListLiveData.switchMap { list ->
        //  Will source  LiveData  Convert to one of the values of  CoroutineLiveData
        liveData(Dispatchers.Default) {
            emit( list.filter { it.startsWith("discount") } )
        }
    }
}

Among them switchMap() yes LiveData Extension method of , It's right Transformations.switchMap() Encapsulation , Used to facilitate chained calls :

public inline fun <X, Y> LiveData<X>.switchMap(
    crossinline transform: (X) -> LiveData<Y>
): LiveData<Y> = Transformations.switchMap(this) { transform(it) }

switchMap() Internal source LiveData Each value of is converted to a new LiveData And subscribe to .

liveData It's a top-level method , Used to build CoroutineLiveData

public fun <T> liveData(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)

CoroutineLiveData Will be updated LiveData The operation of the value is encapsulated in a pending method , The thread of execution can be specified through the context of the collaboration .

Use CoroutineLiveData The following dependencies need to be added :

implementation  "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

Reference resources

Jetpack MVVM Four of the seven deadly sins : Use LiveData/StateFlow send out Events

Recommended reading

The interview series is listed below :

Interview questions | How to write a good and fast log Library ?( One )

Interview questions | How to write a good and fast log Library ?( Two )

Interview questions | Write one with your bare hands ConcurrentLinkedQueue?

Let's discuss it Android What kind of questions should I ask in an interview ? 

RecyclerView Interview questions | Under what circumstances will table entries be recycled to the cache pool ?

Interview questions | Have you ever used concurrency containers ? Yes ! For example, the network requests buried points

原网站

版权声明
本文为[A bird carved in the desert]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050941163030.html