概述
EventBus是Android開發(fā)最常用的一個庫了,它給我們帶來了很好便利性,輕松實現(xiàn)消息的發(fā)布和訂閱。但凡事都有兩面性,合理的使用就是好事,不合理的使用可能就會有各種問題了,下面我們來看看它的源碼實現(xiàn)。
源碼分析
EventBus使用簡單,注冊反注冊發(fā)送監(jiān)聽都非常簡單,易上手。來看一下它的使用吧。
注冊:EventBus.getDefault().register(this);
發(fā)布:EventBus.getDefault().post(event);
反注冊:EventBus.getDefault().unRegister(this);
先看看EventBus的getDefault方法
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
就是獲取EventBus實例,這里用的是全局單例,方便使用。
再看看register方法的實現(xiàn)
public void register(Object subscriber) {
// 獲取觀察者的類型
Class<?> subscriberClass = subscriber.getClass();
// 通過subscriberMethodFinder找到這個觀察者里所有的監(jiān)聽方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 把類和監(jiān)聽的方法關(guān)聯(lián)起來,放在map中,后續(xù)有事件時才能找到對應(yīng)關(guān)系觸發(fā)監(jiān)聽方法
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
一起看一下subscriberMethodFinder是怎么打到觀察者里的監(jiān)聽方法的
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 先從Cache里找,如果有對應(yīng)關(guān)系了直接返回,沒有則去建立對應(yīng)關(guān)系
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
// 這里有個判斷,是否忽略編譯時用工具生成的index文件
// EventBus有一個工具可以在編譯期間就把觀察者和注解的監(jiān)聽方法關(guān)聯(lián)起來,生成一個java文件,避免運行時通過反射進行查找,加快了運行速度,提高性能
if (ignoreGeneratedIndex) {
// 通過反射的方式去找到所有注解的監(jiān)聽方法
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 通過編譯生成的java文件進行查找
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// 找到后put到cache里
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
代碼通過注釋說清楚了監(jiān)聽方法的過程,通過反射就是找使用了@Subscribe注解的函數(shù),并讀取其中的配置,比如threadMode等,更于后面發(fā)送事件時使用。這里就不貼反射的具體代碼了。
要說的就是SubscriberMethod類了,看看這里有哪些成員變量:
public class SubscriberMethod {
final Method method; //方法,用于反射回調(diào)的
final ThreadMode threadMode; //指定的線程
final Class<?> eventType; //事件的類型,后續(xù)要通過這個找相關(guān)的監(jiān)聽方法
final int priority; //優(yōu)先級
final boolean sticky; //是否接收粘性事件
}
找到所有監(jiān)聽的方法后,會通過subscribe方法進行關(guān)聯(lián),來看具體實現(xiàn):
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
// Subscription是監(jiān)聽方法subscriberMethod和觀察者subscriber綁定關(guān)系,用于后面反射調(diào)用方法的
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// subscriptions是此監(jiān)聽方法中的事件類型對應(yīng)的所有subscriptions,是以事件類型為key的
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 如果不存在則新建一個,再把新找到的SubscriberMethod添加進去,這樣后面發(fā)送此事件類型時它才能被觸發(fā)
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// 這里是根據(jù)優(yōu)先級調(diào)整位置
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 這里是從另一個角度看了,獲取觀察者所有監(jiān)聽的事件類型,之前是根據(jù)事件類型找不同觀察者的方法,這里是根據(jù)觀察者類找它所有監(jiān)聽的事件類型,這是為了反注冊時用的
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 如果方法是支持粘性事件的,就會從粘性事件緩存里找到最近一次的事件,直接通過checkPostStickyEventToSubscription進行事件發(fā)送
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
總結(jié)下注冊的邏輯:
- 找到觀察者里所有的監(jiān)聽方法
- 循環(huán)把所有監(jiān)聽方法和事件類型的關(guān)系保存下來
- 如果支持粘性事件就找最近一次同類型事件進行發(fā)送
再來看下發(fā)送的源碼實現(xiàn);
先看下相關(guān)的類說明:
/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>(); //事件隊列
boolean isPosting; //是否正在發(fā)送
boolean isMainThread; //是否在主線程發(fā)送的
Subscription subscription; //監(jiān)聽者的信息
Object event; //事件本身
boolean canceled; //是否已取消
}
public void post(Object event) {
// 獲取PostingThreadState,把事件加到事件隊列中
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
// 初始化其它成員變量
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
// 這里是發(fā)送事件的方法
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
postSingleEvent->postSingleEventForEventType->postToSubscription這里發(fā)送事件方法的調(diào)用路徑,postSingleEventForEventType其實就是通過事件類型從我們注冊時存儲的對應(yīng)關(guān)系里找到此事件對應(yīng)的所有監(jiān)聽信息Subscriptions,然后逐個調(diào)用。我們直接看postToSubscription的實現(xiàn):
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
// 這里都是通過invokeSubscriber反射的形式調(diào)用監(jiān)聽方法的
// 默認(rèn)值,哪個線程拋出的事件就在哪個線程處理
case POSTING:
invokeSubscriber(subscription, event);
break;
// 主線程處理,判斷發(fā)送者是否在主線程,如果是,直接調(diào)用,如果不是,切換到主線程調(diào)用
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
// 后臺線程處理,如果發(fā)送者在主線程,切換到后臺線程調(diào)用,如果不是,直接調(diào)用
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
// 總是空閑線程去調(diào)用,它和BACKGROUND的區(qū)別就是總是開一個新線程去處理,BACKGROUND如果發(fā)送線程本來就是非主線程,就直接在當(dāng)前線程處理了
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
postSticky的實現(xiàn)只是在調(diào)用前把事件先存到stickyEvents,用于注冊支持sticky的監(jiān)聽方法時使用,后面還是調(diào)用了post方法。
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
總結(jié)下發(fā)送的邏輯:
- 如果是粘性事件就把事件存起來
- 通過事件類型找到所有相關(guān)的監(jiān)聽方法
- 通過反射在指定的處理線程中調(diào)用監(jiān)聽方法
最后看下反注冊的源碼實現(xiàn):
public synchronized void unregister(Object subscriber) {
// 根據(jù)觀察者找到所有監(jiān)聽的事件類型
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
// 把此觀察者從事件類型對應(yīng)的所有方法中移除
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
總結(jié)下反注冊的邏輯:
就是把注冊時存儲的對應(yīng)關(guān)系一一解除。
總結(jié)
EventBus的優(yōu)點:
- 易用性:使用簡單易上手
- 輕耦合:使用它就是為了替換回調(diào)函數(shù),避免模塊間的強耦合
- 線程切換:EventBus通過注解里的threadMode可以輕松指定處理的線程
EventBus的缺點:
代碼變得不易讀,不好發(fā)現(xiàn)哪里調(diào)用了事件發(fā)送及哪里做了事件;
如果項目里濫用了,會出現(xiàn)要區(qū)分來源的問題。
舉個例子,請求獲取一個實時性比較強的數(shù)據(jù),不同時間請求的結(jié)果會不一樣,請求結(jié)果回來后用EventBus拋出結(jié)果,需要的進行監(jiān)聽。A界面執(zhí)行了請求,請求沒結(jié)束前就跳轉(zhuǎn)到B界面,B界面同樣調(diào)用了這個請求,這里結(jié)果回來的速度不一樣,B界面就會收到兩個EventBus的事件,導(dǎo)致處理錯誤。
解決辦法有兩個:
- A界面跳轉(zhuǎn)到B界面后,取消A界面的請求,避免重復(fù)數(shù)據(jù)發(fā)送(正常都要做的事情)
- 對所有的事件增加來源標(biāo)識(可以用這個界面類的hashcode),B界面收到后可以判斷來源是否是自己發(fā)的請求,如果不是,則不處理。但這就要求發(fā)請求時要把這個標(biāo)識帶上了。
EventBus基本上是開發(fā)應(yīng)用必備的第三方庫了,總得來說好處多多,但還是要適當(dāng)使用,別看它那么好用就一沖動就把所有Callback換成EventBus實現(xiàn),那樣會得不償失的。