Data backflow phenomenon
I believe many people already know about the problem of LiveData's "data backflow", so I'll mention it here. The so-called "data backflow": It is actually similar to sticky broadcast. When a new observer starts to register for observation, the last historical data sent last time will be passed to the currently registered observer .
For example, in the following example code:
val testViewModel = ViewModelProvider(this)[TestViewModel::class.java]
testViewModel.updateData("第一次发送数据")
testViewModel.testLiveData.observe(this,object :Observer<String>{
override fun onChanged(value: String) {
println("==============$value")
}
})
updateData
The method sends data once, and when the LiveData observe
method is called below, it will be printed immediately ==============第一次发送数据
. This is the "data backflow" phenomenon mentioned above.
cause
The reason is actually very simple. In fact, there is LiveData
an internal mVersion
field, record version, whose initial value mVersion
is -1. When we call it setValue
or postValue
it mVersion
will +1
; for each observer's encapsulation ObserverWrapper
, its initial value mLastVersion
is also -1
, that is, For each newly registered observer, its mLastVersion
is -1; when LiveData
setting this ObserverWrapper
, if LiveData
the is mVersion
greater ObserverWrapper
than the current one will be forced to be mLastVersion
pushed to .LiveData
value
Observer
That is the following code
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
// 判断observer的版本是否大于LiveData的版本mVersion
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
So to solve this problem, there are two ways of thinking:
- By changing
ObserverWrapper
the value of each version number - In some way, ensure that the first distribution does not respond
Solution
Currently there are three solutions available on the Internet
Only respond once at a time
public class SingleLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean mPending = new AtomicBoolean(false);
public SingleLiveData() {
}
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
super.observe(owner, (t) -> {
if (this.mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
});
}
@MainThread
public void setValue(@Nullable T t) {
this.mPending.set(true);
super.setValue(t);
}
@MainThread
public void call() {
this.setValue((Object)null);
}
}
This method can solve the problem of sending historical data back, but Observe
it does not work for multiple monitors. It can only be a single monitor. If there are multiple monitors, only one can receive it normally, and the others will not work properly.
reflection
This method is to obtain the version number of LiveData through reflection every time an observer is registered, and then modify the version number value of the current Observer through reflection. The advantages of this approach are:
Observer
Ability to monitor multiple- Solve the stickiness problem
But there are also disadvantages:
- Every
observer
time you register, you need to reflect the updated version, which is time-consuming and has performance issues.
UnPeekLiveData
public class SingleLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean mPending = new AtomicBoolean(false);
public SingleLiveData() {
}
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
super.observe(owner, (t) -> {
if (this.mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
});
}
@MainThread
public void setValue(@Nullable T t) {
this.mPending.set(true);
super.setValue(t);
}
@MainThread
public void call() {
this.setValue((Object)null);
}
}
This is actually SingleLiveData
an upgraded version of the above. SingleLiveData
It uses one variable to control all Observers, and each Observer
one used above uses a control identifier for control. Every setValue
time, all Observer
switches are turned on to indicate that distribution can be accepted. After distribution, turn off the currently executed Observer
switch, that is, it cannot be executed for the second time unless you restart setValue
. This method is basically perfect for price comparison, except that there is an extra one inside to HashMap
store each Observer
logo. If Observer
there are more, there will be a certain amount of memory consumption.
new ideas
Let’s first look at LiveData
how to get the version number:
int getVersion() {
return mVersion;
}
This method is a package access permission method. If I create a new LiveData
class with the same package name, can I get this value without reflection? Actually this is feasible
// 跟LiveData同包名
package androidx.lifecycle
open class SafeLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
// 直接可以通过this.version获取到版本号
val pictorialObserver = PictorialObserver(observer, this.version > START_VERSION)
super.observe(owner, pictorialObserver)
}
class PictorialObserver<T>(private val realObserver: Observer<in T>, private var preventDispatch: Boolean = false) :
Observer<T> {
override fun onChanged(value: T) {
// 如果版本有差异,第一次不处理
if (preventDispatch) {
preventDispatch = false
return
}
realObserver.onChanged(value)
}
}
}
The idea behind this tricky approach is:
- The version number can be obtained by using the access permission of the same package name, without obtaining it through reflection.
- Determine
LiveData
whetherObserver
there is a version difference between the sum and the sum. If there is, it will not respond for the first time, otherwise it will respond.
I personally prefer this approach and have also applied it to actual development. The advantages of this method are: small changes, no need for reflection, no need for HashMap
storage, etc. The disadvantages are: it is somewhat intrusive. If the access rights of the later method are modified or the package name is changed, it will be invalid, but I think This possibility is relatively small. After all, the androidx library has been iterated for so many versions and is relatively stable.
Android study notes
Android performance optimization article: Android Framework underlying principles article: Android vehicle article: Android reverse security study notes: Android audio and video article: Jetpack family bucket article (including Compose): OkHttp source code analysis notes: Kotlin article: Gradle article: Flutter article: Eight knowledge bodies of Android: Android core notes: Android interview questions from previous years: The latest Android interview questions in 2023: Android vehicle development position interview exercises: Audio and video interview questions:https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J
https://qr18.cn/F05ZCM
https://qr18.cn/CQ5TcL
https://qr18.cn/Ei3VPD
https://qr18.cn/A0gajp
https://qr18.cn/Cw0pBD
https://qr18.cn/CdjtAF
https://qr18.cn/DzrmMB
https://qr18.cn/DIvKma
https://qr18.cn/CyxarU
https://qr21.cn/CaZQLo
https://qr18.cn/CKV8OZ
https://qr18.cn/CgxrRy
https://qr18.cn/FTlyCJ
https://qr18.cn/AcV6Ap