我们经常会遇到所谓的粘性事件,具体什么是粘性事件呢?我们可以看一个例子,存在两个Activity,分别为FirstActivity和SecondActivity, 我们在FirstActivity中先发射了数据,然后进入了SecondActivity中,在SecondActivity中监听LiveData的变化,但是我们会很惊奇的发现,SecondActivity在FirstActivity发射之后才注册监听LiveData的事件,居然也能收到以前的发射数据。这就有些和我们的尝试相悖了,我们一般的常识,是先注册监听事件了才会有事件回调,并且注册事件是不管之前发生的逻辑的。
我们可以看一下具体表现形式:
首先我们从发送消息开始
liveData.postValue("页面1 发送的消息")
发送消息有两个方法:setValue和postValue
setValue 只能在主线程使用,postValue可以在任何线程使用,它被调用时,通过handler切换到主线程,再调用 setValue
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
这里有个 mVersion 要注意一下,后面会用到。然后就是通过 dispatchingValue 方法来分发消息了。
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, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
在这个方法里,主要是参数传的观察者是否为空,如果不为空,则向此观察者分发消息,如果为空,将会从观察者集合里面遍历观察者,进行分发。在这里,我们主要看 considerNotify 方法。
private void considerNotify(ObserverWrapper observer) {
...............
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
在我们前面提到 mVersion 用到了这里,和mLastVersion 做了比较,这点我们在下个步骤进行说明。
这就是我们全部发送消息的过程了,很简单明了,但是还不足以窥全貌,接下来我们分析另外一个步骤,监听。
liveData.observe(this) {
Log.e("TAG", "onCreate: ")
tv.setText("监听到的消息:$it")
}
• 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);
• ............
• owner.getLifecycle().addObserver(wrapper);
• }
在这里,主要是对我们的 owner 和observer 做了一层包装,然后让 lifecycle 进行了监听。然后我们就看看 包装了点什么
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer super T> observer) {
super(observer);
mOwner = owner;
}
.........
@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());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
............
}
可以看到 LifecycleBoundObserver 继承了 ObserverWrapper ,实现了 LifecycleEventObserver 接口。
LifecycleEventObserver 接口 主要是当 lifecycle 状态改变的时候会感应到,并进行回调。
然后我们主要看看父类 ObserverWrapper :
private abstract class ObserverWrapper {
final Observer super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer super T> observer) {
mObserver = observer;
}
............
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
}
是不是看到了一个熟悉的面孔,就是我们上个步骤 提到的 mLastVersion ,它是在这里定义的,并且默认是-1;这里会在后文进行贯穿起来,先了解它的源头。
接下来,我们先继续走流程,还是LifecycleBoundObserver 类中,当状态改变的时候,会调用 onStateChanged 方法
@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());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
当活跃状态改变的时候,会 调用 activeStateChanged :
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
当状态是活跃状态的时候,会调用 dispatchingValue 进行数据分发,我们上文用到的分发是遍历所有观察者进行数据分发,这次是只分发当前观察者。
通过上一篇文章的源码解析,我们可以清晰的了解到,LiveData判断这个事件是否分发出去的关键在considerNotify方法中。
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
每次setValue或postValue时,mVersion会+1,只要mLastVersion>=mVersion即证明之前有过setValue或postValue。现在我们想使在observer调用前的setValue方法不被分发出去,只需要在调用observer之前的某个节点处改,变使其mLastVersion = mVersion即可。
通过源码我们发现可以通过反射在observer中找到mObservers对象和当前mVersion,然后便可以在这里将mVersion赋值给mLastVersion。
private void hook(@NonNull Observer observer) throws Exception {
//get wrapper's version
Class classLiveData = LiveData.class;
Field fieldObservers = classLiveData.getDeclaredField("mObservers");
fieldObservers.setAccessible(true);
Object objectObservers = fieldObservers.get(this);
Class> classObservers = objectObservers.getClass();
Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
methodGet.setAccessible(true);
Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
Object objectWrapper = null;
if (objectWrapperEntry instanceof Map.Entry) {
objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
}
if (objectWrapper == null) {
throw new NullPointerException("Wrapper can not be bull!");
}
Class> classObserverWrapper = objectWrapper.getClass().getSuperclass();
Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
fieldLastVersion.setAccessible(true);
//get livedata's version
Field fieldVersion = classLiveData.getDeclaredField("mVersion");
fieldVersion.setAccessible(true);
Object objectVersion = fieldVersion.get(this);
//set wrapper's version
fieldLastVersion.set(objectWrapper, objectVersion);
}
}
然后重写继承重写LiveData,将这个hook方法放在observe方法中。
这样一来,使用该自定义的LiveData时就会发现,先setValue,后observe的做法已经行不通了,这就是所谓的非粘性。
##方法二:使用 SingleLiveEvent
SingleLiveEvent,顾名思义,是一个只会发送一次更新的 LiveData。其代码实现如下:
public class SingleLiveEvent extends MutableLiveData {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
compareAndSet:比较并设置。 m.compareAndSet(a,b),如果ma ,返回true,同时将m置为b; 如果mb,返回false。
其实这个方法解决的并不是粘性事件的问题,而是“数据倒灌”的问题。“数据倒灌”一词出自KunMinX的Blog重学安卓:LiveData 数据倒灌 背景缘由全貌 独家解析,即在setValue后,observe对此次set的value值会进行多次消费。比如进行第二次observe的时候获取到的数据是第一次的旧数据。这样会带来不可预期的后果。
val msg = MutableLiveData>()
msg.value = Event("1")
button3.setOnClickListener {
msg.observe(this,MyObs())
}
class MyObs :Observer>{
override fun onChanged(t: Event) {
t.getContentIfNotHandled()?.let { Log.e(">>>", it) }
}
}
多次点击button3,会多次回调onChanged。实际上,只有第一次数据是我们想要的。SingleLiveEvent的思路是,在每次onChanged触发时,会通过一个布尔值mPending来判断上一次的setValue事件有没有被消费,如果被消费过了,则不再将消费传递下去。
实际上,SingleLiveEvent并没有解决‘粘性’的问题。
它所适用的场景如代码中所示,你一次setValue后,多次observe,却只想消费一个observe。但是,SingleLiveEvent 的问题在于它仅限于一个观察者。如果您无意中添加了多个,则只会调用一个,并且不能保证哪一个。
其实思路和第三种差不多,不过把其逻辑封装到了外面一层,这就解决了上文中只能添加一个观察者的问题,并且可以在外层增加一些自己独有的业务逻辑,使用起来更加优雅。
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
//----------------------------使用时 --------------------------------
val l = MutableLiveData>()
l.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
...
}
})
所以其解决的问题也还是“数据倒灌”的问题,并非“粘性事件”。
这个是KunMinX大神所开源的一个解决此类问题的方法。
public class ProtectedUnPeekLiveData extends LiveData {
protected boolean isAllowNullValue;
private final HashMap observers = new HashMap();
public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer super T> observer) {
LifecycleOwner owner = activity;
Integer storeId = System.identityHashCode(observer);//源码这里是activity.getViewModelStore(),是为了保证同一个ViewModel环境下"唯一可信源"
observe(storeId, owner, observer);
}
private void observe(@NonNull Integer storeId,
@NonNull LifecycleOwner owner,
@NonNull Observer super T> observer) {
if (observers.get(storeId) == null) {
observers.put(storeId, true);
}
super.observe(owner, t -> {
if (!observers.get(storeId)) {
observers.put(storeId, true);
if (t != null || isAllowNullValue) {
observer.onChanged(t);
}
}
});
}
@Override
protected void setValue(T value) {
if (value != null || isAllowNullValue) {
for (Map.Entry entry : observers.entrySet()) {
entry.setValue(false);
}
super.setValue(value);
}
}
protected void clear() {
super.setValue(null);
}
}
其思路也很清晰,为每个传入的observer对象携带一个布尔类型的值,作为其是否能进入observe方法的开关。每当有一个新的observer存进来的时候,开关默认关闭。
每次setValue后,打开所有Observer的开关,允许所有observe执行。
同时方法进去后,关闭当前执行的observer开关,即不能对其第二次执行了,除非你重新setValue。
通过这种机制,使得 不用反射技术实现LiveData的非粘性态 成为了可能。
最后,要说明下文章中出线的粘性和数据倒灌两个词。
粘性:具体代码中指的是,先setValue/postValue,后调用observe(),如果成功收到了回调,即为粘性事件。
数据倒灌:“数据倒灌”一词最先由大佬KunMinX提出,虽然给出了示例,但并没有给出文字定义。我的理解是,先setValue/postValue,后调用observe(new Obs()),至此收到了回调。然后再第二次调用observe(new anotherObs()),如果还能收到第一次的回调,则为“数据倒灌”。
所以只要将LiveData变为“非粘性”的,就一定不会出现数据倒灌的问题了。再看以上四种方法所解决的问题。
以上就是有关framework中的LiveData 粘性事件原理分析及解决的四个常用办法。属于framework技术中的一个小点;真正想要成为framework高级工程师还需要掌握许多;这里我可以带给各位一些学习framework的文档资料;可以点击这里获取↓↓↓领取!
framework全家桶都在里面,内容讲解的技术点很细节。希望能帮助到大家!