一文搞懂 LiveData 粘性事件 和 数据倒灌

文章目录

  • 前言
  • LiveData使用
  • 分析粘性事件
  • 分析数据倒灌
  • 总结
一、前言

在使用LiveData的时候,会有疑问,LiveData 是怎么做到先发送再注册,依然能够接收到事件的。还有就是会碰到切换屏幕,导致重复的操作,也就是所谓的数据倒灌。这篇文章将一一揭晓。

二、LiveData使用

我们写一个CounterViewModel 继承 ViewModel,然后声明一个_counter变量。
通过点击事件触发incrementCounter() 给_counter.value 赋值,每次都加一。
这里就是通过LiveData 的 setValue 发送。

class CounterViewModel : ViewModel() {
    private val _counter = MutableLiveData<Int>()
    val counter: LiveData<Int>
        get() = _counter

    init {
        _counter.value = 0
    }
    
    fun incrementCounter() {
        val count = _counter.value ?: 0
        _counter.value = count + 1
    }
}

接着我们在Activity中的进行注册监听,我们通过ViewModelProvider 获取 viewModel 对象,
然后通过observe 进行注册监听事件。实际是通过onChanged
进行监听setValue传过来的值。
viewModel.counter.observe(this, object: Observer {
override fun onChanged(t: Int?) {
Log.d(“ssz”, “值:$t”)
}
})
下面是lambda 简化写法,实际还是通过onChanged 进行数据的接收的

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    binding = DataBindingUtil.setContentView(this, R.layout.activity_test_mvvm)
    viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
    binding.viewModel = viewModel
    binding.lifecycleOwner = this
    viewModel.counter.observe(this, {
          t -> Log.d("ssz", "值:$t")
      }
    )
    Log.d("ssz", "onCreate")
  }

总的来说,LiveData 通过setValue 发送,然后通过 Observer 里面的onChanged 进行接收。

三、分析粘性事件

通过上面的例子,我们确实能接收到数据了。那么什么是粘性事件呢,就是我们先发送再注册,这个时候依然能接收到消息,那么这就是粘性事件。就好比是我们熟悉的EventBus中的粘性事件。
那么LiveData 真的有粘性事件吗,我们打个日志就知道了,还是上面的例子,只是加上这个日志。

    init {
        _counter.value = 0
        Log.d("ssz", "初始化counter值")
    }

这是上面代码执行后的日志:
在这里插入图片描述
可看到我们是先执行了_counter.value 的初始化,也就是数据的发送,然后才接着执行oncreate中observe中的监听的。说明了LiveData 具有粘性事件。

然后这个时候,你如果正常点击,那么这个时候,就是先注册再发送了。这个就是回到正常先注册再发送了。

那么LiveData 是怎么做到粘性事件的呢。我们看下源码:

下面这是数据发送的源码:

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

这是注册监听observe 的关键源码:

   @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue()); //这是关键
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

dispatchingValue 会调用 下面的 considerNotify,也是粘性事件的真正原因

  private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) { //关键1
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData); //关键2
    }

mLastVersion 的默认初始值是-1,mVersion 的默认初始值也是-1,当我们先执行发送的时候,进行了自增,mVersion 就变成了0,当我们执行observe 进行监听的时候,observer.mLastVersion >= mVersion 这个条件就不成立了,因为此时mLastVersion 是-1,小于 mVersion 了。因为这时 mVersion 是0。

那么就不会return,就会继续执行接下来的代码
observer.mObserver.onChanged((T) mData);
看到了没有,这个onChanged 就是我们observe 里头的 onChanged。这就把数据传给了监听了。

这就是为什么,我们先发送也能接收到数据的原因了,也就是LiveData 所谓的粘性事件了。

这个时候你可能有个疑问,那正常是怎么样的呢?
正常先注册,后发送。

这里是注册的关键源码:

 @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());  //关键2
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

这是发送源码:

    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null); //关键1
    }

这里我们发现setValue 会去调用上面的dispatchingValue 就是会去调用注册的onChanged。一步步看。
我们能看到关键2,这里就是执行我们上面分析的considerNotify了。因为我们是先注册,再发送的,注册的 observer.mLastVersion 还是默认值-1,而发送还是老样子 mVersion 会自增,变成0。和上面分析的一样,observer.mLastVersion 小于 mVersion。这样就会执行后面的observer.mObserver.onChanged((T) mData),也就是我们注册时候的onChanged,我们就能在onChanged 接收数据了。

小结:上面我们分析了先注册后发送,还有先发送后注册的两种情况。

四、分析数据倒灌

上面能通过LiveData进行数据的发送和接收,但是当你进行横竖屏的切换,你会发现日志跟着打印的,你并没有点击发送数据呀,为什么会这样呢,这是因为当你横竖屏切换的时候,Activity进行了重新的创建,onCreate重新走了一遍。

你可能想,我这里还是走得LiveData的注册啊,但是这个时候,源码里头重新创建了个监听事件,重新new 了一个新的观察者,(相当于是上面我们提到的先发送,后注册)也就是粘性事件起了作用。

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

new LifecycleBoundObserver 重新new了一个观察者,这个LifecycleBoundObserver 其实继承了ObserverWrapper 也就是我们的mLastVersion 初始化的类。

因为重新创建了新的观察者,也就是observer.mLastVersion 重新变为初始值-1,而mVersion因为前面的发送数据,可能很多次发送了,已经大于等于0了。所以,这个时候一定会执行observer.mObserver.onChanged((T) mData)。就我们上面分析的粘性事件。这个时候,你就会收到最后一次发送的数据。

那么怎么解决呢?

1、保证Activity进行了不重新创建,也就是oncreate 不重复执行。
2、通过使用SingleLiveEvent。

第一种
很简单,直接在清单中我们配置下,比如android:configChanges=“orientation|screenSize” 保证旋转屏幕不会重新创建Activity。当然还有系统文字变化,根据自己的需要进行配置。目的保证不会重新创建Activity就行了。

第二种,
我们这里重点讲下SingleLiveEvent:

open class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner, {
            if (pending.compareAndSet(true, false)) {//这里是关键
                observer.onChanged(it)
            }
        })
    }

    @MainThread
    override fun setValue(value: T?) {
        pending.set(true)
        super.setValue(value)
    }

    @MainThread
    fun call() {
        value = null
    }
}

使用方法:

private val _snackbarText = SingleLiveEvent<String>()
val snackbarText: LiveData<String> = _snackbarText

fun onSnackbarClick() {
    _snackbarText.value = "Snackbar Clicked"
}    
   viewModel.snackbarText.observe(this, { text ->
      // 显示 Snackbar
      val rootView: View = findViewById(android.R.id.content)
      Snackbar.make(rootView, text, Snackbar.LENGTH_SHORT).show()
      Log.d("ssz","点击了" + text)
    })

我们新建一个SingleLiveEvent 继承MutableLiveData,然后通过重写observe,在里头进行判断,一旦执行过就不再执行,除非下一次的发送。这样就保证,不会数据倒灌的发生。

源码地址:https://github.com/shenshizhong/LiveDataDemo

总结

1 、简单介绍LiveData 的使用
2 、分析了粘性事件是怎么产生的,
3、 分别对先注册后发送,和先发送后注册进行了分析。
3 、分析了数据倒灌怎么回事,以及怎么处理

如果对你有一点点帮助,那是值得高兴的事情。:)
我的csdn:http://blog.csdn.net/shenshizhong
我的简书:http://www.jianshu.com/u/345daf0211ad

你可能感兴趣的:(android,开发语言,kotlin)