LiveData 事件发送原理解析

2,397 阅读10分钟

都说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"); 
    }

我们看下打印的日志情况

image.png 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感知宿主生命周期机制) image.png 29则在ReportFragment里面对Activity注册ActivityLifecycleCallbacks监听,通过回调触发 image.png 这里以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。 image.png

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}")
        }
    }

我们等页面显示出来后,点击按钮,再次触发订阅,此时会出现所谓的数据倒灌? image.png 因为第二个观察者调用了两次就说是数据倒灌?其实原因很简单,那就是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状态的到达而触发值分发,此时就只会收到一次值回调,这个值也是最新的值。 image.png

五、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())
    }
}

当页面显示后,我们点击按钮,此时才订阅,发现如下日志 image.png 就像👆说的,状态依次分发,会依次打印日志,你能说这是Lifecycle的粘性事件吗?

那最后,小伙伴们还认为Livedata存在粘性事件或者数据倒灌吗?