当前位置:网站首页>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 :
- Whether the data observer is active .
- Whether the lifecycle component bound by the data observer is active .
- 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 :
- When the value is updated , Traverse all observers and distribute the latest values to them .
- 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 ofthis
. 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 isTransformations.map()
, It USESMediatorLiveData
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 throughCoroutineLiveData
.
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 ?
边栏推荐
- 【C语言】动态内存开辟的使用『malloc』
- B站大量虚拟主播被集体强制退款:收入蒸发,还倒欠B站;乔布斯被追授美国总统自由勋章;Grafana 9 发布|极客头条...
- Kotlin compose multiple item scrolling
- Apache dolphin scheduler system architecture design
- Six simple cases of QT
- 【OpenCV 例程200篇】219. 添加数字水印(盲水印)
- Those who are good at using soldiers, hide in the invisible, and explain the best promotional value works in depth in 90 minutes
- 横向滚动的RecycleView一屏显示五个半,低于五个平均分布
- RMS TO EAP通过MQTT简单实现
- .Net之延迟队列
猜你喜欢
随机推荐
.Net之延迟队列
Swift tableview style (I) system basic
Apache dolphin scheduler system architecture design
Cut off 20% of Imagenet data volume, and the performance of the model will not decline! Meta Stanford et al. Proposed a new method, using knowledge distillation to slim down the data set
历史上的今天:第一本电子书问世;磁条卡的发明者出生;掌上电脑先驱诞生...
【系统设计】指标监控和告警系统
Uni app running to wechat development tool cannot Preview
LiveData 面试题库、解答---LiveData 面试 7 连问~
能源势动:电力行业的碳中和该如何实现?
天龙八部TLBB系列 - 关于技能冷却和攻击范围数量的问题
硬核,你见过机器人玩“密室逃脱”吗?(附代码)
Data visualization platform based on template configuration
H. 265 introduction to coding principles
A high density 256 channel electrode cap for dry EEG
Unity粒子特效系列-毒液喷射预制体做好了,unitypackage包直接用 - 上
Unity particle special effects series - the poison spray preform is ready, and the unitypackage package is directly used - on
卷起來,突破35歲焦慮,動畫演示CPU記錄函數調用過程
Roll up, break 35 - year - old Anxiety, animation Demonstration CPU recording Function call Process
> Could not create task ‘:app:MyTest.main()‘. > SourceSet with name ‘main‘ not found.问题修复
Swift set pickerview to white on black background