重复造轮子之LiveDataBus与PageEventBus
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重复造轮子之LiveDataBus与PageEventBus相关的知识,希望对你有一定的参考价值。
背景
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(Android OkHttp使用与分析
重复造轮子,编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)