都说LiveData存在粘性事件or数据倒灌,真的吗,我不信。
一、回顾一下EventBus的粘性事件
//A页面先发送粘性事件
EventBus.getDefault().postSticky(event);
//B页面添加EventBus事件接收 sticky = true
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
public void show5(MyMessage myMessage) {
Log.e("eventBus1", "show5: 接收数据message");
}
//B页面姗姗来迟的注册
EventBus.getDefault().register(BActivity.this);
A页面先发送粘性事件后,打开B页面,此时B页面注册EventBus后就可以收到这个事件。现在来看看EventBus的粘性事件的原理。
//from EventBus
private final Map<Class<?>, Object> stickyEvents= new ConcurrentHashMap<>();
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
......
}
//注册
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
.......
if (subscriberMethod.sticky) {
.....;
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
}
EventBus对象实例是个单例,持有一个粘性事件相关的ConcurrentHashMap来保存粘性事件,然后当相关页面注册时,可以基于反射或者注解生成的类来找到对应的事件Method,然后反射调用对应的方法,触发粘性事件。
Q: EventBus的粘性事件是有专门的功能需要,那liveData所谓的粘性事件是咋回事呢?
二、LiveData所谓的“粘性事件”
粘性事件:事件发送后,观察者才订阅,订阅后会收到之前的事件。
class MyViewModel : ViewModel() {
val msg = MutableLiveData<Int>(1); //初始化值
var count = 1
fun getRemote() {
//假设这里耗时,然后用post切换下线程
count++;
msg.postValue(count)
}
}
class BActivity : AppCompatActivity() {
var viewModel: MyViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sec)
viewModel = ViewModelProvider(this, NewInstanceFactory()).get(MyViewModel::class.java)
viewModel?.getRemote() //先生产个数据
viewModel?.msg?.observe(this) {
Log.e("test", "观察者--》${it}")
}
Log.e("test", "onCreate");
}
override fun onStart() {
super.onStart()
Thread.sleep(4000) //onStart耗时
}
override fun onResume() {
super.onResume()
Log.e("test", "onResume");
}
我们看下打印的日志情况
Q1: 按EventBus的搞法应该在订阅后立即就发送给订阅者,为啥onStart sleep耗时会影响它的发送?
Q2: 明明viewModel?.getRemote()先调用,为啥onResume也能在我们前面打印?
三、LiveData订阅源码解析
先看livedata的注册事件,一个observer订阅者,竟然订阅了两个被订阅者。
viewModel?.msg?.observe(this) {
Log.e("test", "观察者--》${it}")
}
//from liveData
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
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);
}
// from LifecycleRegistry
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
......
}
通常观察者发起订阅时,就会被被订阅者管理起来,这里很显然我们的订阅者observer被mObservers(SafeIterableMap<Observer<? super T>, ObserverWrapper>)就是一map保存起来了,而且
owner.getLifecycle().addObserver(wrapper)也会被一个mObserverMap( FastSafeIterableMap<LifecycleObserver, ObserverWithState> )保存起来,被订阅者(一个是BActivity,一个是MutableLiveData)。
Activity的生命周期方法感知,主要还是依赖Lifecycle这个库,其中targetsdk29版本与之前版本的Lifecycle的事件分发触发机制稍有不同。
低于29的Lifecycle事件分发主要借助嵌入到Activity中ReportFragment的生命周期方法回调触发(类Glide感知宿主生命周期机制)
29则在ReportFragment里面对Activity注册ActivityLifecycleCallbacks监听,通过回调触发
这里以29为例
static class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
static void registerIn(Activity activity) { //就是在这里注册监听
activity.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
}
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
dispatch(activity, Lifecycle.Event.ON_CREATE);
}
@Override
public void onActivityPostStarted(@NonNull Activity activity) {
dispatch(activity, Lifecycle.Event.ON_START);
}
@Override
public void onActivityPostResumed(@NonNull Activity activity) {
dispatch(activity, Lifecycle.Event.ON_RESUME);
}
......
}
很明显,当BActivity的onCreate方法走完了,才会回调onActivityPostCreated,CREATED事件分发开始.
ReportFragment--->dispatch(activity,event)
LifecycleRegistry--->observer.dispatchEvent(lifecycleOwner, event);
LiveData.LifecycleBoundObserver--->onStateChanged(source,event)
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
removeObserver(mObserver); //页面销毁,就解除注册,两个订阅都会解除的
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
activeStateChanged(shouldBeActive()); //根据state来判断能否激活
currentState = mOwner.getLifecycle().getCurrentState();
}
}
本以为,订阅Activity的目的只是为了当页面onDestroy时,就解除对livedata和Activity的注册,殊不知,这里除了onDestroy逻辑,还有其他逻辑。
@Override
boolean shouldBeActive() { //当前状态至少是STARTED状态才能被激活
return mOwner.getLifecycle().getCurrentState().compareTo(STARTED)>= 0;
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive) { //激活后就尝试分发值
dispatchingValue(this);
}
}
此时我们的状态是CREATED,所以newActive就是false,默认的mActive也为false就直接return。over了。很明显,对事件分发(dispatchingValue)有限制,订阅者的lambda回调不是一订阅就能立马收到回调,而是有个激活操作,而且这个激活操作是在onStart方法调用完之后才能做的,激活后会尝试分发值,有值就分发,没有就不分发,我们在onStart方法中sleep了4s,那就必须等4s过了之后,等到状态达到STARTED才能dispatchingValue。
//LivdeData的构造
START_VERSION=-1;
public LiveData(T value) {
mData = value;
mVersion = START_VERSION + 1;
}
这个版本mVersion是用来跟Data绑定的吗,难道会出现CAS的ABA问题?非也,暂且认为mData更新一次,版本就会增加,数据重置,就为-1。如果此时到了STARTED状态,此时mActive就true了,那么就会分发我们的值dispatchingValue(this)。
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) { //防重
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
......
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) { //没有激活就不能(不配)发送值
return;
}
if (!observer.shouldBeActive()) { //没到STATED状态也不配发送值
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) { //上一次的版本值已经是最新的值就无需再发送了
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData); //就是回调lambda里面的逻辑
}
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
} //走主线程handler的post逻辑
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);
}
};
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
observer.mLastVersion默认值是-1,现在的情况是,livedata的dispatchingValue有两处会触发:1、STATED状态到了,要准备分发值;2、用户在onCreate里面msg.postValue(count)手动也会触发。
到底谁先谁后呢 or 二者会不会同时发生呢?
肯定是handler在后面触发,我猜测,可能是因页面的绘制是在onResume,在绘制前,会捕获下次vsync信号来临的回调,方便同频绘制,此时会发送同步屏障消息,导致我们的普通消息被延后执行了。但是按理说,同步屏障消息应该比我们的普通消息插入的晚,不会对我们的普通消息有影响的啊,此处有疑问,劳烦小伙伴们帮忙解答?
二者不可能同时发生,因为都是在主线程,只能是串行。
如果先执行,肯定就是在onStart后打印1,等到onResume后,handler执行打印2。
Q:那如果多次出现>STARTED的Activity的生命周期回调,会不会触发多次事件分发?
不会,因为有个flag-->mActive,一旦触发过一次,就是true,所以页面的生命周期回调只会影响一次。
3.1、前后台切换时特殊场景处理
Q:插播一个测试点:如果ViewModel请求数据时,此时将app手动置于后台,当postValue后,会收到订阅回调吗?如果收不到,将页面拉回到前台,此时能收到?
具体测试操作: 我们将getRemote方法改造一下,模拟耗时调用。等页面都完全展示了,此时点击按钮,会触发这个getRemote方法。置于后台等个5s以上,那postValue肯定会执行,但此时是不会收到订阅回调,当我们将页面从后台切到前台,此时就能收到回调了。(ps: handler在后台也会执行任务的哈)
class MyViewModel : ViewModel() {
val msg = MutableLiveData<Int>(1);
var count = 1
fun getRemote() {
//假设这里耗时,然后用post切换下线程
Thread{
count++
Thread.sleep(5000)
msg.postValue(count);
}.start()
}
}
//BActivity中我们等页面都完全展示了,此时点击按钮
fun onClick(view: android.view.View) {
viewModel?.getRemote()
}
当置于后台时,此时页面BActivity会调用onStop,这就有意思了,按照前面的逻辑,会不会分发STOPED状态,其实不会,最大的状态是RESUMED,那就不对等了,确实,EVENT是EVENT,state是state他们没有一一对应。
//from LifeCycle
public enum Event {
ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP,ON_DESTROY,ON_ANY;
.....
@NonNull
public State getTargetState() {
switch (this) {
case ON_CREATE:
case ON_STOP: //这里是关键啊
return State.CREATED;
case ON_START:
case ON_PAUSE: //ON_START ON_PAUSE竟然都是State.STARTED
return State.STARTED;
case ON_RESUME:
return State.RESUMED;
case ON_DESTROY:
return State.DESTROYED;
case ON_ANY:
break;
}
throw new IllegalArgumentException(this + " has no target state");
}
}
public enum State {
DESTROYED,INITIALIZED, CREATED,STARTED,RESUMED;
//这个是很关键的方法呢
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
状态名跟事件名不是一一对应的关系,订阅者在刚订阅的时候,默认状态是INITIALIZED的,我们这里的状态变更是这样的,页面完全展示,act就是onResume调用了,此时状态走到State.RESUMED(枚举STATE中的最大值),如果此时开始调用getRemote方法,置于后台,页面的onPause调用后会进入State.STARTED,之后页面会调用onStop,此时会进入State.CREATED状态,这就有点恶心了,onStop不对应State.STOPED状态(因为没有State.STOPED状态),对应的是State.CREATED,这点很关键,
State.CREATED肯定是小于State.STARTED。源码调用如下:
LifecycleBoundObserver--->onStateChanged
LifecycleBoundObserver---->activeStateChanged
LiveData--->dispatchingValue
LiveData--->considerNotify
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) { //首次onStart()方法调用完后就激活了,肯定还是活的,
return;
}
if (!observer.shouldBeActive()) { //此时是State.CREATED 且活的
observer.activeStateChanged(false);//那就给你失活
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
@Override
boolean shouldBeActive() { //激活的最低标准要是STATE.STARTED
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) { //newActive此时是false,失活
return;
}
mActive = newActive; //失活了
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
看上面的代码,很容易得出,当前是State.CREATED且是激活状态,此时就需要让它失活,因为激活条件的最低标准是 达到State.STARTED,此时就应该立马失活,当postValue进行值分发的时候,就会发现,当前是失活的,分发不了了,订阅者的订阅回调不走了。
LiveData--->dispatchingValue
LiveData--->considerNotify
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) { //已经失活了,分发不了值了。
return;
}
....
}
分发值的逻辑里,它知道可能分发会不成功,就像本次一样,所以它必须保存这个值,实际上在分发时确实会先将值保存在mData,且对应的版本的也会++,期待下次分发。
当我们的页面从后台回到前台的时候,此时Act的onRestart调用,但是ReportFragment没有对应的方法,所以不会回调,再次调用onStart后,就会分发STATE.STATED状态,很关键的,就再次激活,此刻激活,就会分发一次值,订阅回调就收到了。(ps: Activity的方法调用顺序从后台到前台 onRestart--onStart---onResume)
四、LiveData所谓的“数据倒灌”
我们在BActivity添加一个点击事件,再次发起一个订阅
class MyViewModel : ViewModel() {
val msg = MutableLiveData<Int>(1); //初始化值
var count = 1
fun getRemote() {
//假设这里耗时,然后用post切换下线程
count++;
msg.postValue(count)
}
}
class BActivity : AppCompatActivity() {
var viewModel: MyViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sec)
viewModel = ViewModelProvider(this, NewInstanceFactory()).get(MyViewModel::class.java)
viewModel?.getRemote()
viewModel?.msg?.observe(this) {
Log.e("test", "观察者--》${it}")
}
Log.e("test", "onCreate");
}
override fun onStart() {
super.onStart()
Thread.sleep(4000)
}
override fun onResume() {
super.onResume()
Log.e("test", "onResume");
}
fun onClick(view: android.view.View) {
viewModel?.getRemote();
viewModel?.msg?.observe(this){
//observer观察者
Log.e("test","第2个观察者--》${it}")
}
}
我们等页面显示出来后,点击按钮,再次触发订阅,此时会出现所谓的数据倒灌?
因为第二个观察者调用了两次就说是数据倒灌?其实原因很简单,那就是viewModel?.getRemote();这里会postValue,走handler事件分发,肯定比后面的主线程执行的代码慢,那就先走订阅流程,订阅那里,在LifecycleRegistry的addObserver方法中,默认添加进去的observer的状态为INITIALIZED。
//from LifecycleRegistry
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
enforceMainThreadIfNeeded("addObserver"); //那就是INITIALIZED状态了
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
......
//添加进来时,状态跟目前状态不匹配,就依次分发事件,等到直到状态相等才停止
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
.....
statefulObserver.dispatchEvent(lifecycleOwner, event);
.....
}
}
public enum State {
DESTROYED,INITIALIZED, CREATED,STARTED,RESUMED;
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
此刻都能点击页面了,说明页面状态到达了RESUMED,就会触发dispatchEvent,然后onStateChanged,从枚举State可知,状态从INITIALIZED到RESUMED要触发3次onStateChanged,但是真正只有STARTED会产生影响,所以这里就会先打印一个2,这个2就是liveData的旧值,等到handler的post执行完成后,就会再发送一次,此时就打印3了。
如果,我们使用 msg.value=count呢?
fun onClick(view: android.view.View) {
viewModel?.getRemote();
viewModel?.msg?.observe(this){
Log.e("test","第2个观察者--》${it}")
}
}
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
那么值会先被修改,因为没有订阅,只是将mData设置了新值,mVersion++了而已,等到订阅的时候,就会因为STARTED状态的到达而触发值分发,此时就只会收到一次值回调,这个值也是最新的值。
五、Lifecycle也存在粘性事件?
先举个Lifecycle常用的🌰
class TestLifecycle implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void aa(){
Log.e("test"," @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void bb(){
Log.e("test"," @OnLifecycleEvent(Lifecycle.Event.ON_START)");
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void cc(){
Log.e("test"," @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)");
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sec)
}
fun onClick(view: android.view.View) {
lifecycle.addObserver(TestLifecycle())
}
}
当页面显示后,我们点击按钮,此时才订阅,发现如下日志
就像👆说的,状态依次分发,会依次打印日志,你能说这是Lifecycle的粘性事件吗?
那最后,小伙伴们还认为Livedata存在粘性事件或者数据倒灌吗?