前言
何为粘性事件?
即发射的事件如果早于注册,那么注册之后依然可以接收到的事件称为粘性事件
背景
最近接手了一个公司的项目,采用了目前比较新的技术:LiveData+ViewModel的事件通知框架。该框架拥有大量的优点包括但不仅限于以下:1.实时感知生命周期。2.无需手动回收,解绑,即不会出现内存泄漏的情况。3.数据变化可进行实时通知 等等......
本人对于这个框架也只是一知半解,奈何项目比较紧急,接手以后马上就要开始干活,所以只能是边干活边了解内部实现原理。
结果项目中就碰到了一个让我头疼了整整一天的问题。那么到底是啥呢,请接着往下看↓
起因
由于公司代码不便放到网上,所以本人便以demo代码代替(主要逻辑一致)
public class CustomViewModel extends ViewModel {
MutableLiveData mLiveData;
private int mPostedValue = 10;
public MutableLiveData getLiveData(){
if(mLiveData == null){
mLiveData = new MutableLiveData<>();
}
loadData();
return mLiveData;
}
private void loadData() {
new Thread(){
@Override
public void run() {
SystemClock.sleep(2000);
mLiveData.postValue(mPostedValue);
mPostedValue = mPostedValue * 2;
}
}.start();
}
}
这个就是ViewModel的代码,用于进行网络请求的操作,返回数据以后通过LiveData实时刷新
public class MainActivity extends AppCompatActivity {
private CustomViewModel mViewModel;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
final MutableLiveData liveData = mViewModel.getLiveData();
liveData.observe(this, new Observer() {
@Override
public void onChanged(Integer integer) {
Log.e(TAG,"参数返回: " + integer);
}
});
}
@Override
protected void onResume() {
super.onResume();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MutableLiveData liveData = mViewModel.getLiveData();
liveData.observe(MainActivity.this, new Observer() {
@Override
public void onChanged(Integer integer) {
Log.e(TAG,"参数返回: " + integer);
}
});
}
},5000);
}
}
此为activity里面的代码,onCreate会率先订阅一个LiveData的事件,然后观察网络请求回调。onResume里面的代码模仿的就是点击事件再次进行网络请求。
现象
打印结果如下
2019-09-14 18:48:23.710 6745-6745/com.netease.livedatademo E/MainActivity: 参数返回: 10
2019-09-14 18:48:26.720 6745-6745/com.netease.livedatademo E/MainActivity: 参数返回: 10
2019-09-14 18:48:28.721 6745-6745/com.netease.livedatademo E/MainActivity: 参数返回: 20
2019-09-14 18:48:28.721 6745-6745/com.netease.livedatademo E/MainActivity: 参数返回: 20
首先看到这个结果,肯定不是我想要的结果。下面的20打印了两次,不过这个现象倒是一眼就能看出来,毕竟onCreate的时候已经订阅过一次,你发射的第二次数据自然就会有两个观察者可以监听到。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
final MutableLiveData liveData = mViewModel.getLiveData();
liveData.observe(this, new Observer() {
@Override
public void onChanged(Integer integer) {
Log.e(TAG,"参数返回: " + integer);
//解除观察者
liveData.removeObserver(this);
}
});
}
所以在onCreate的注册方法里面加上了解除注册的操作。嗯,完美了,打印下看看
2019-09-14 18:54:17.465 6973-6973/com.netease.livedatademo E/MainActivity: 参数返回: 10
2019-09-14 18:54:20.483 6973-6973/com.netease.livedatademo E/MainActivity: 参数返回: 10
2019-09-14 18:54:22.483 6973-6973/com.netease.livedatademo E/MainActivity: 参数返回: 20
事情总是不能如人所愿。。。发现这时候10依然多打了一次,那么问题来了,这个10到底是哪里来的。(我在公司代码里面全局搜索了一遍,发射数据的地方就这么一个,于是就很费解到底怎么回事)
既然事情结果是这样了,肯定不能就这么下去,于是开始了源码探究,既然onCreate里面的注册已经解除,那么基本没什么必要在往下看下去了,所以打算从onResume调用的注册方法开始下手
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
LiveData在调用Observer以后最后会执行addObserver方法,而此处getLifeCycle获取的对象是LifecycleRegistry对象(原因不过多追究,不是本文重点)
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
...代码省略...
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}
...代码省略...
}
此处会进入while循环,然后调用dispatchEvent方法,所以我们接着往下看
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);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
然后会调用LifeCycleBoundObserver的onStateChanged方法
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
}
private abstract class ObserverWrapper {
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
...代码省略
do {
mDispatchInvalidated = false;
if (initiator != null) {
//会执行此方法
considerNotify(initiator);
initiator = null;
}
...代码省略
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
最终会判断mLastVersion是否比mVersion大,如果小于mVersion,那么会调用onChanged方法,即我们在MainActivity里面注册的事件。那么mLastVersion和mVersion是什么,接着往下看:
private int mVersion = START_VERSION;
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
我们发现mVersion初始值是-1,这个mVersion是属于LiveData的,然后只在setValue(postValue最终也会调用setValue)的时候会自增1,那么mLastVersion呢?
int mLastVersion = START_VERSION;
发现mLastVersion也是初始值为-1,而这个mLastVersion是属于ObserverWrapper的,而赋值的地方只有在比较完才会赋值.
那么仔细回想下,我们其实在onCreate那里注册的观察者信息其实有过一次setValue的操作了,又因为我们其实用的是同一个LiveData,所以mVersion最后是会自增1的,又因为ObserverWrapper在每次注册的时候都会重新new,所以mLastVersion每次都是-1开始。
那么真相大白了,只要之前有发射过一次数据,那么后面注册的观察者都会接收到之前发射过的数据,而且看样子这个Version值不可以轻易改变,也就是说谷歌不提供API让我们取消掉粘性事件(这算是一个比较大的缺点了)
解决方案
问题来了,既然已经知道了原因,那么怎么解决呢?不可能放着不管的。方案有三,容我一一道来
1.既然每次注册时ObserverWrapper是不一样的,那么只要我们的LiveData也不一样不就可以轻松解决了?
2.不要多次注册:onCreate里面的注册以后,onResume里面就不要注册了,然后通过判断条件的不同写两个不同的处理方式。
3.既然无法直接修改mVersion值,和mLastVersion值。那么我们可以直接重写LiveData类。然后重写Observer接口,通过重写的Observer类的onChange方法进行拦截。那么怎么拦截?
在LiveData的Observe方法里面将传入的Observer对象装饰到我们自己的Observer类里面,然后调用super.Observe的时候将我们自己的Observer方法传入,然后就可以进行自定义拦截。代码如下:
public class BaseLiveData extends MutableLiveData {
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer super T> observer,boolean isSticky) {
if(isSticky){
super.observe(owner, observer);
} else {
super.observe(owner,new CustomObserver(observer));
}
}
@Override
public void setValue(T value) {
super.setValue(value);
}
@Override
public void postValue(T value) {
super.postValue(value);
}
class CustomObserver implements Observer {
private Observer super T> mObserver;
public CustomObserver(Observer super T> observer) {
mObserver = observer;
}
@Override
public void onChanged(T t) {
//此处做拦截操作
mObserver.onChanged(t);
}
}
}
总结
总的来说解决方案不算复杂,前面两种更是没有什么难度。只不过谷歌不提供API让我们可以解除LiveData的粘性事件确实有点霸道。所以就这点来说远没有EventBust来的灵活,当然LiveData生命感知的能力确实是EventBus无法比拟的。只能说各取所需吧