背景
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時解注冊
結語
重復造輪子可以說是我們每個開發的比經之路,為什么要重復造輪子,現成的庫它不香嗎?一方面我們的需求會千奇百怪,自己的輪子可以更好的服務我們的需求,另一方面也可以加深自己對某方面的理解,學會怎么造一個好用的輪子,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/301076.html
標籤:其他
