粘性事情产生原因

咱们经常会遇到所谓的粘性事情,详细什么是粘性事情呢?咱们能够看一个比如,存在两个Activity,分别为FirstActivity和SecondActivity, 咱们在FirstActivity中先发射了数据,然后进入了SecondActivity中,在SecondActivity中监听LiveData的变化,可是咱们会很惊讶的发现,SecondActivity在FirstActivity发射之后才注册监听LiveData的事情,居然也能收到曾经的发射数据。这就有些和咱们的尝试相悖了,咱们一般的常识,是先注册监听事情了才会有事情回调,而且注册事情是不论之前产生的逻辑的。

咱们能够看一下详细表现形式:

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<Map.Entry<Observer<? super T>, 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) {
•       // ignorereturn;
•     }
•     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 进行数据分发,咱们上文用到的分发是遍历一切观察者进行数据分发,这次是只分发当时观察者。

粘性事情问题的处理方案

方法一:反射干与Version

经过上一篇文章的源码解析,咱们能够清晰的了解到,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);
}

每次setValuepostValue时,mVersion会+1,只要mLastVersion>=mVersion即证明之前有过setValuepostValue。现在咱们想使在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),假如m==a ,回来true,一起将m置为b; 假如m==b,回来false。

其实这个方法处理的并不是粘性事情的问题,而是“数据倒灌”的问题。“数据倒灌”一词出自KunMinX的Blog重学安卓:LiveData 数据倒灌 布景缘由全貌 独家解析,即在setValue后,observe对此次set的value值会进行屡次消费。比如进行第2次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
        ...
    }
})

所以其处理的问题也仍是“数据倒灌”的问题,并非“粘性事情”。

方法四:UnPeekLiveData

这个是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开关,即不能对其第2次履行了,除非你重新setValue。

经过这种机制,使得 不必反射技术完成LiveData的非粘性态 成为了可能。

粘性与数据倒灌

最后,要说明下文章中出线的粘性数据倒灌两个词。

粘性:详细代码中指的是,先setValue/postValue,后调用observe(),假如成功收到了回调,即为粘性事情。

数据倒灌:“数据倒灌”一词最先由大佬KunMinX提出,虽然给出了示例,但并没有给出文字界说。我的理解是,先setValue/postValue,后调用observe(new Obs()),至此收到了回调。然后再第2次调用observe(new anotherObs()),假如还能收到第一次的回调,则为“数据倒灌”。

所以只要将LiveData变为“非粘性”的,就必定不会出现数据倒灌的问题了。再看以上四种方法所处理的问题。

反射干与Version SingleLiveEvent 事情包装器 UnPeekLiveData
将“粘性”变为“非粘性”
处理“数据倒灌”

以上便是有关framework中的LiveData 粘性事情原理分析及处理的四个常用方法。属于framework技术中的一个小点;真实想要成为framework高级工程师还需要把握许多。

结束

这儿带给我们一张LiveData 粘性事情原理图;供我们参阅总结。

LiveData 粘性事件(原理+四个解决方法)