背景
Android的消息总线框架近几年流行莫过于的EventBus,RxBus,一般来讲它已经足够好用,简洁、解耦,我们能够很方便的进行消息传递,那为什么我们现在又要再造一个消息总线的框架的轮子呢?
这就要说到今天的主角LiveData了,LiveData天生为观察者模式,与EventBus的功能有很大重合,而且作为jetpack的一员,与Lifecycle和ViewModel有很好配合,现在网上也有很多要用LiveData取代EventBus的声音,它主要有以下优点:
- 感知生命周期
- 不需要调用反注册方法
这里盗一张图,看一下LiveData的结构:
EventBus发送的消息是全局,在某些情况下,我们发送的消息只希望在页面内部接收到,例如同一个Activity的两个Fragment,这时候使用Event就很麻烦,而传统方式是利用Activity作为桥梁,使用接口的方式进行消息传递,但这样需要定义接口和回调,又太过麻烦,这时候就轮到LiveData登场了(其实还要加上ViewModel),这其实也是Google推荐的实现方式。下面我们主要讲讲复用LiveData实现方式
具体实现
使用LiveData实现消息总线非常简单,一个文件即可解决,网上很多例子,这里非常简单的实现了一下:
public class LiveEventBus {
public static LiveEventBus get() {
return LiveEventBusHolder.instance;
}
private static class LiveEventBusHolder {
static LiveEventBus instance = new LiveEventBus();
}
private Map<String, MutableLiveData<? extends LiveBusEvent>> mBus = new ArrayMap<>();
@NonNull
public <T extends LiveBusEvent> MutableLiveData<T> of(@NonNull Class<T> clazz) {
String eventName = clazz.getName();
MutableLiveData liveData = mBus.get(eventName);
if (liveData == null) {
liveData = new MutableLiveData();
mBus.put(eventName, liveData);
}
return liveData;
}
public <T extends LiveBusEvent> void post(@NonNull T event) {
MutableLiveData liveData = of(event.getClass());
liveData.setValue(event);
}
interface LiveBusEvent {
}
}
监听消息
LiveEventBus.get()
.of(MyEvent.class)
.observe(this, new Observer<MyEvent>() {
@Override
public void onChanged(MyEvent myEvent) {
}
});
发送消息
LiveEventBus.get().post(new MyEvent("hello world"));
这样一个事件总线就完成了。
但是,LiveData谡消息总线,有一个非常严重的问题,那就是它只支持粘性事件,也就是说,如果我们先发送一个消息,再通过observe监听消息,那么就会立刻接收到一个消息,这其实是我们不希望看到的,所以接下来主要需要解决粘性事件的问题。
粘性事件分析及解析
这个问题其实已经有很多文章分析过了,这里就不重复分析了,大家可以看看美团的这篇文章: Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus
说一下关键原因,在LiveData中有一个int类型的成员变量version
public abstract class LiveData<T> {
static final int START_VERSION = -1;
private int mVersion = START_VERSION;
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
}
这个version是用来记录LiveData设置值的次数的,初始为-1,每次加1,而注册observer时,如果当Lifecycle处理活动状态后会立刻分发事件,最后会立刻回调下面这个方法:
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);
}
observer是一个ObseverWraper,为Observer的包装类,mLastVersion的值初始都会-1,所以新注册一个类时,如果之前只要有设置过值,LiveData的mVersion就不会为-1,会比 observer.mLastVersion,我们注册的Observer就会接收到事件。
所以要解决粘性事件的问题,自然而然的就会想到去改变version的值,然而不管是mVersion还是mLastVersion,它们都是private的,无法直接拿到,所以就催生出反射获取的方法,美团的这篇文章就是这样解决的。
反射总归是不安全的,这里提一下我的解决方法:既然LiveData内部的成员我们无法修改,那为什么不自己实现,记录自己的version值呢?
public class BusLiveData<T> extends MutableLiveData<T> {
private static final int VERSION_START = -1;
private int activeVersion = VERSION_START;
private ArrayMap<Observer<? super T>, BusObserver<? super T>> busObservers = new ArrayMap();
@Override
public void setValue(T value) {
activeVersion++;
super.setValue(value);
}
private BusObserver<? super T> createBusObserver(@NonNull Observer<? super T> observer, int latestVersion) {
BusObserver<? super T> busObserver = busObservers.get(observer);
if (busObserver == null) {
busObserver = new BusObserver(observer, latestVersion);
busObservers.put(observer, busObserver);
} else {
throw new IllegalArgumentException("Please not register same observer " + observer);
}
return busObserver;
}
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
super.observe(owner, createBusObserver(observer, activeVersion));
}
public void observeSticky(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
super.observe(owner, createBusObserver(observer, VERSION_START));
}
@Override
public void observeForever(@NonNull Observer<? super T> observer) {
super.observeForever(createBusObserver(observer, activeVersion));
}
public void observeStickyForever(@NonNull Observer<T> observer) {
super.observeForever(createBusObserver(observer, VERSION_START));
}
@Override
public void removeObserver(@NonNull Observer<? super T> observer) {
BusObserver<? super T> busObserver;
if (observer instanceof BusObserver) {
busObserver = (BusObserver) observer;
} else {
busObserver = busObservers.get(observer);
}
if (busObserver != null) {
busObservers.remove(busObserver.realObserver);
super.removeObserver(busObserver);
}
}
private class BusObserver<M extends T> implements Observer<M> {
private Observer<M> realObserver;
private int lastVersion;
public BusObserver(@NonNull Observer<M> realObserver, int lastVersion) {
this.realObserver = realObserver;
this.lastVersion = lastVersion;
}
@Override
public void onChanged(M m) {
if (activeVersion <= lastVersion) {
return;
}
lastVersion = activeVersion;
if (m != null) {
realObserver.onChanged(m);
}
}
}
}
由于在大部分情况下,消息传递是需要非粘性,我将observe的默认方法改成了非粘性的,并且提供了observeSticky来进行粘性事件的监听。
页面内通信
说到LiveData,就不得不提到ViewModel,所以要在页面页通信,只需要实现一个通用的ViewModel即可observeSticky单独出来使用
public class PageEventBus extends ViewModel {
private static PageEventBus obtain(@NonNull ViewModelStoreOwner owner) {
return new ViewModelProvider(owner, FACTORY).get(PageEventBus.class);
}
//省略其它
public static ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new PageEventBus();
}
};
}
通过自定义Event来发送消息
PageEventBus通过class来区分发送消息的类别,这个参考了EventBus,所以发送消息前需要先定义一个Event类,为规范使用,且必需继承LiveBusEvent接口,例如:
class FilterSearchEvent(val tag: String, val map: Map<String, Any>) : LiveBusEvent
activity包含一个ViewPager, ViewPager中有多个Fragment,Activity需要给Fragment发送消息,可以这样写
在Fragemnt onViewCreated
之后注册监听,PageEventBus.get
的参数必需为Activity或者Context,这样才能获取到Activity
的ViewModel
. observe时传入viewLifecycleOwner
,viewLifecycleOwner
为Fragment生命周期特有的一个类,可以感知Fragment的onCreateView和onDestroyView的生命周期
PageEventBus.get(requireActivity())
.of(FilterSearchEvent::class.java)
.observe(viewLifecycleOwner, Observer {
DuLogger.d("$mTag FilterSearchEvent: ${it.map}")
if (it.tag != cateKey) return@Observer
scrollTopRefresh()
})
在Activity中发送消息
PageEventBus.get(this)
.post(FilterSearchEvent(tag = filterHelper.getSelectTag(), map = map))
通过eventName发送消息
每个类型的事件都需要自定义类对于一些情况可能是不必要的,这里提供一种根据事件名称区分消息的方法 例如,我们要向商详发送名为pd_refresh_event
的消息,先监听
PageEventBus.get(this)
.ofEmpty("pd_refresh_event")
.observe(this, Observer {
getProductDetail()
})
再发送消息
PageEventBus.get(this).postEmpty("pd_refresh_event")
可以看到,和自定义Event区别在于注册时用的ofEmpty, 发送用的postEmpty
全局通信
用法和PageEventBus一样,只是要将PageEventBus换成LiveEventBus,效果和EventBus一样
使用技巧与注意事项
postLatest
在使用LiveData时,我们会发现它提供了两个方法,分别为setValue和postValue, setValue只能在主线程中使用,postValue可以任意线程中使用,但实际上,postValue并不只是通过handle.post
那么简单, 我们可以看一下它的源码:
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
可以看到,每次都会对mPendingData
赋值,但在处理这次任务前只会post一次,这样防止了多次无意义的设置值。
为了防止混淆LiveEventBus/PageEventBus
提供了post和postLatest分别对应LiveData的setValue
和postValue
, postLatest利用的是LiveData的post特性,将连续使用postLatest时,监听的处只会发收到一最后一条消息,作为消息通知时建议使用
例如:
LiveEventBus.get()
.of(HitEvent::class.java)
.observe(this, Observer {
LogUtils.d("LiveEventBus HiltEvent receive: $it")
doRefresh()
})
//点击时,连续发送多个事件,只会收到最后一个
LiveEventBus.get().postLatest(HitEvent("HiltEvent 111"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 222"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 333"))
点击时连续发送3个事件,只会收到最后一个
LiveEventBus HiltEvent receive: HitEvent(content=HiltEvent 333)
kotlin中内部类的问题
在上面那个例子中,如果Observer中没有任何与外部类相关的代码,如下:
LiveEventBus.get()
.of(HitEvent::class.java)
.observe(this, Observer {
LogUtils.d("LiveEventBus HiltEvent receive: $it")
})
这里监听只打印了log,但是在打开多个Acitivity时,在第二次执行相同代码时会报如下错误:
Caused by: java.lang.IllegalArgumentException: Please not register same observer com.tory.demo.jetpack.HiltDemoActivity$initView$5@68383e
at com.tory.library.utils.livebus.BusLiveData.createBusObserver(BusLiveData.java:36)
at com.tory.library.utils.livebus.BusLiveData.observe(BusLiveData.java:46)
at com.tory.library.utils.livebus.BusObservableWrapper.observe(BusObservableWrapper.java:63)
at com.tory.demo.jetpack.HiltDemoActivity.initView(HiltDemoActivity.kt:70)
at com.tory.library.base.BaseActivity.onCreate(BaseActivity.kt:30)
at com.tory.demo.jetpack.Hilt_HiltDemoActivity.onCreate(Hilt_HiltDemoActivity.java:29)
at android.app.Activity.performCreate(Activity.java:7894)
at android.app.Activity.performCreate(Activity.java:7881)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3283)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3457)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7560)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
这个报错是表示这个LiveData相同的Observer关联了不同的Lifecycle, 那这里的Observer为什么会是相同的对象呢, 如果是java,这肯定是匿名内部类,每次都是一个新的对象才对。为找到原因,我们把kotlin反编译成java可以看一下,成了如下这个样子
LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)null.INSTANCE);
可以看到,Observer被优化成了一个常量INSTANCE!!!。但是如果我们在Observer中加一段有关外部类的内部,就不会出问题了,如果
LiveEventBus.get()
.of(HitEvent::class.java)
.observe(this, Observer {
LogUtils.d("LiveEventBus HiltEvent receive: $it $this")
})
反编译成如下样子
LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)(new Observer() {
// $FF: synthetic method
// $FF: bridge method
public void onChanged(Object var1) {
this.onChanged((HitEvent)var1);
}
public final void onChanged(HitEvent it) {
LogUtils.d("LiveEventBus HiltEvent receive: " + it + ' ' + HiltDemoActivity.this);
}
}));
说到底,这是由于kotlin对匿名内部类的优化造成的,与外部类无关的匿名内部类会被优化成常量。
在View或者ViewHolder中监听
在View中,我们一般是拿不到LifecyclerOwner对应的,而且有进View被移除了就需要我们把监听移除,这里也提供了方法,例如:
PageEventBus.get(context)
.of(AddressSelectEvent::class.java)
.observe(this, Observer {
currentAddressId = it.addressId
notifyRefresh()
})
这里observe的this指的是View本身,它会在onViewAttachedToWindow时真正注册,onViewDetachedFromWindow时解注册
结语
重复造轮子可以说是我们每个开发的比经之路,为什么要重复造轮子,现成的库它不香吗?一方面我们的需求会千奇百怪,自己的轮子可以更好的服务我们的需求,另一方面也可以加深自己对某方面的理解,学会怎么造一个好用的轮子。