事件总线相信大家很多时候都会用到,那大家常用的也就是常青树 EventBus,以及 RxJava 流行起来的后起之秀 RxBus。它们的使用方式都差不多,思想也都是基于观察者模式,正好 LiveData 的核心思想也是观察者模式,因此我们完全可以使用 LiveData 来实现一个事件总线,如果项目使用 Jetpack 套件的话,可以减少库的依赖,并且如果使用 LiveData 的话,还可以将注销操作交给系统处理,在避免内存泄露方面又可以省心了。
一、基本实现
我们需要实现不同界面之间通信的话,必然是需要使用同一个 LiveData 对象,那么首先我们就需要一个 LiveData 的管理类,将 LiveData 对象用一个键值对集合存储起来,Key 为 LiveData 中 value 的权限类名,Value 就是 LiveData 对象,然后考虑到可以会使用带有泛型的对象作为事件传递,所以在注册 LiveData 的时候最好还支持泛型。有了这两个基本需求,我们就可以简单设计一下我们的管理类 LiveEventBus:
package com.qinshou.jetpackdemo.liveeventbus;
import androidx.lifecycle.MutableLiveData;
import java.util.HashMap;
import java.util.Map;
public class LiveEventBus {
private static final Map<String, MutableLiveData<?>> sMap = new HashMap<>();
public static <T> MutableLiveData<T> get(Class<T> clazz) {
String key = clazz.getName();
MutableLiveData<T> eventLiveData = (MutableLiveData<T>) sMap.get(key);
if (eventLiveData == null) {
eventLiveData = new MutableLiveData<>();
sMap.put(key, eventLiveData);
}
return eventLiveData;
}
public static <T> MutableLiveData<T> get(TypeToken<T> typeToken) {
String key = typeToken.getType().toString();
MutableLiveData<T> eventLiveData = (MutableLiveData<T>) sMap.get(key);
if (eventLiveData == null) {
eventLiveData = new MutableLiveData<>();
sMap.put(key, eventLiveData);
}
return eventLiveData;
}
}
TypeToken 其实就是类似 Gson 中的 TypeToken 类,用于获取带泛型的对象中的真实泛型的,如果项目中 json 解析是用的 Gson 的话,那么也可以直接使用 Gson 的 TypeToken 类。由于功能简单,所以我是自己定义了 TypeToken 类:
package com.qinshou.jetpackdemo.liveeventbus;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class TypeToken<T> {
private Type mType;
public TypeToken() {
mType = new Type() {
@Override
public String getTypeName() {
Type genericSuperclass = TypeToken.this.getClass().getGenericSuperclass();
if (!(genericSuperclass instanceof ParameterizedType)) {
return genericSuperclass.toString();
}
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length <= 0) {
return parameterizedType.toString();
}
return actualTypeArguments[0].toString();
}
@Override
public String toString() {
Type genericSuperclass = TypeToken.this.getClass().getGenericSuperclass();
if (!(genericSuperclass instanceof ParameterizedType)) {
return genericSuperclass.toString();
}
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length <= 0) {
return parameterizedType.toString();
}
return actualTypeArguments[0].toString();
}
};
}
public Type getType() {
return mType;
}
}
其实到此为止,我们已经可以使用 LiveEventBus 实现不同组件间通信了,eg:
findViewById<Button>(R.id.btn_observe_int).setOnClickListener {
LiveEventBus.get(Int::class.java).observe(this) {
Toast.makeText(this@LiveEventBusActivity,"接受到 Int 事件: $it",Toast.LENGTH_LONG).show()
}
}
findViewById<Button>(R.id.btn_observe_string_list).setOnClickListener {
LiveEventBus.get(object : TypeToken<List<String>>() {}).observe(this) {
Toast.makeText(this@LiveEventBusActivity,"接受到 List<String> 事件: $it",Toast.LENGTH_LONG).show()
}
}
findViewById<Button>(R.id.btn_post_int).setOnClickListener {
LiveEventBus.get(Int::class.java).value = 123
}
findViewById<Button>(R.id.btn_post_string_list).setOnClickListener {
LiveEventBus.get(object :
TypeToken<List<String>>() {}).value = listOf("Hello LiveEventBus")
}
先点击上面两个按钮注册观察者后,再点击下面两个按钮发送事件,效果如下:
可以看到效果跟使用 EventBus 是一样的,而且由于 LiveData 的天然特性,我们还不用去关注注销操作,可谓是省了一心。
二、粘性事件
使用过 LiveData 的朋友应该知道在 LiveData 绑定的时候会收到最新的值(不知道的朋友可以参考我的另一篇博客 Jetpack 之 LiveData_禽兽先生不禽兽的博客-CSDN博客),其效果就是先发送事件再注册观察者的话,也能收到之前的事件,如下:
这可以说是 LiveData 的一个优势,但是在作为事件总线时,这个特点相当于 EventBus 的粘性事件,我们有时候并不需要粘性事件,所以我们需要对 LiveData 进行一下改造。
定义一个 BaseObserver:
public abstract class BaseObserver<T> implements Observer<T> {
/**
* 是否接收粘性事件
*/
boolean mSticky = true;
public boolean isSticky() {
return mSticky;
}
public BaseObserver<T> setSticky(boolean sticky) {
mSticky = sticky;
return this;
}
}
注意在后面添加观察者的时候我们需要添加的 BaseObserver 的实现类而不是 androidx.lifecycle 包下的 Observer 了
然后我们复制 LiveData 源码,修改两个地方,首先给 ObserverWrapper 增加一个 boolean 属性 mSticky:
private abstract class ObserverWrapper {
...
/**
* 是否接收粘性事件
*/
boolean mSticky = true;
...
}
因为知道是 mLastVersion 造成的这个粘性事件,所以在添加观察者时,根据 BaseObserver 的 mSticky 属性来决定是否同步 mLastVersion:
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);
// 如果是 BaseObserver,则设置 wrapper 对应属性
if (observer instanceof BaseObserver) {
wrapper.mSticky = ((BaseObserver<?>) observer).isSticky();
}
@SuppressLint("RestrictedApi") 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;
}
// 不需要粘性事件,则同步最后版本
if (!wrapper.mSticky) {
wrapper.mLastVersion = mVersion;
}
owner.getLifecycle().addObserver(wrapper);
}
如果有需要的话,observeForever 方法也可以对应修改。另外,LiveEventBus 中对 MutableLiveData 的引用也需要去掉,引入我们自己的 MutableLiveData,所有源码最后会放上链接。
如此改造,我们就可以决定观察者是否接受粘性事件了:
findViewById<Button>(R.id.btn_observe_int).setOnClickListener {
LiveEventBus.get(Int::class.java).observe(this, object : BaseObserver<Int>() {
override fun onChanged(t: Int?) {
Toast.makeText(this@LiveEventBusActivity, "接受到 Int 事件: $it", Toast.LENGTH_LONG).show()
}
}.setSticky(false))
}
findViewById<Button>(R.id.btn_observe_string_list).setOnClickListener {
LiveEventBus.get(object : TypeToken<List<String>>() {}).observe(this, object :
BaseObserver<List<String>>() {
override fun onChanged(t: List<String>?) {
Toast.makeText(this@LiveEventBusActivity, "接受到 List<String> 事件: $it", Toast.LENGTH_LONG).show()
}
}.setSticky(false))
}
先发送事件,再注册观察者,可以看到并没有收到事件,然后再次发送事件的时候才收到:
三、非活跃状态时收到事件
这个问题我也同样在刚才提到的博客中分析过,主要是因为通常我们使用 LiveData 是结合数据请求用的,收到响应后一般就渲染 UI 了,而在后台的时候因为看不到 UI,所以也不关心非活跃状态的时候观察者是否有收到通知,但如果需要在后台分发一下事件的时候,我们也要相应的对其进行改造。
在 BaseObserver 中再增加一个 boolean 属性 mObserveOnlyActive,在 ObserverWrapper 中也同样增加该属性:
public abstract class BaseObserver<T> implements Observer<T> {
/**
* 是否接收粘性事件
*/
boolean mSticky = true;
/**
* 是否仅在活跃状态下接收更新
*/
private boolean mObserveOnlyActive = true;
...
省略 get/set
}
private abstract class ObserverWrapper {
...
/**
* 是否接收粘性事件
*/
boolean mSticky = true;
/**
* 是否仅在活跃状态下接收更新
*/
boolean mObserveOnlyActive = true;
...
}
添加观察者时增加对 mObserveOnlyActive 属性的赋值:
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);
// 如果是 BaseObserver,则设置 wrapper 对应属性
if (observer instanceof BaseObserver) {
wrapper.mSticky = ((BaseObserver<?>) observer).isSticky();
wrapper.mObserveOnlyActive = ((BaseObserver<?>) observer).isObserveOnlyActive();
}
@SuppressLint("RestrictedApi") 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;
}
// 不需要粘性事件,则同步最后版本
if (!wrapper.mSticky) {
wrapper.mLastVersion = mVersion;
}
owner.getLifecycle().addObserver(wrapper);
}
然后再修改 considerNotify 方法中对观察者不活跃就 return 的判断代码即可:
private void considerNotify(ObserverWrapper observer) {
// 如果 observer 不是活跃状态,但 observer 要求仅在活跃状态下通知的,直接 return
if (!observer.mActive && observer.mObserveOnlyActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
// 如果 observer 不应该是活跃状态,但 observer 要求仅在活跃状态下通知的,直接 return
if (!observer.shouldBeActive() && observer.mObserveOnlyActive) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
现在我们将给发送事件加一个延时,然后退到后台使观察者处于非活跃状态,看看还能不能收到事件:
findViewById<Button>(R.id.btn_observe_int).setOnClickListener {
LiveEventBus.get(Int::class.java).observe(this, object : BaseObserver<Int>() {
override fun onChanged(t: Int?) {
Toast.makeText(this@LiveEventBusActivity, "接受到 Int 事件: $it", Toast.LENGTH_LONG).show()
}
}.setSticky(false).setObserveOnlyActive(false))
}
findViewById<Button>(R.id.btn_observe_string_list).setOnClickListener {
LiveEventBus.get(object : TypeToken<List<String>>() {}).observe(this, object :
BaseObserver<List<String>>() {
override fun onChanged(t: List<String>?) {
Toast.makeText(this@LiveEventBusActivity, "接受到 List<String> 事件: $it", Toast.LENGTH_LONG).show()
}
}.setSticky(false).setObserveOnlyActive(false))
}
findViewById<Button>(R.id.btn_post_int).setOnClickListener {
it.postDelayed({ LiveEventBus.get(Int::class.java).value = 123 }, 3000)
}
findViewById<Button>(R.id.btn_post_string_list).setOnClickListener {
it.postDelayed({
LiveEventBus.get(object :
TypeToken<List<String>>() {}).value = listOf("Hello LiveEventBus")
}, 5000)
}
可以看到观察者处于后台也接收到了事件,所以这个问题也得以解决。
四、总结
上面只是简单实现了事件总线的效果,但也由此可见使用 LiveData 来封装事件总线的可行性,对于上述两个关键问题,相信看过 LiveData 源码后对其修改并不难,所以万事还要究其根本。
如果还需要更多如指定观察者线程、观察者优先级的功能,可以参照 EventBus 进行进一步封装。
五、Demo 地址
禽兽先生/JetpackDemo-LiveEventBushttps://gitee.com/MrQinshou/jetpack-demo/tree/master/app/src/main/java/com/qinshou/jetpackdemo/liveeventbus