EventBus 3.0原始碼分析
簡介
EvenntBus 是一個Android開發中的用於事件分發的開源庫。它的工作核心是釋出/訂閱者者模式,它可以利用很少的程式碼來實現多元件間通訊。android的元件間通訊,我們不由得會想到handler訊息機制和廣播機制,通過它們也可以進行通訊,但是使用它們進行通訊,程式碼量多,元件間容易產生耦合引用。關於EventBus的工作模式,這裡引用一張官方圖幫助理解。
為什麼會選擇使用EventBus來做通訊?
- 簡化了元件間交流的方式
- 對事件通訊雙方進行解耦
- 可以靈活方便的指定工作執行緒,通過ThreadMode
- 速度快,效能好
- 庫比較小,60k左右,對包大小無影響
- 使用這個庫的app多,有權威性
- 功能多,使用方便
EventBus的使用也非常簡單,其中三個重要的角色
1:Publisher 事件釋出者
2:Subscriber 事件訂閱者
3:Event 事件
Publisher post 事件後,Subscriber會自動收到事件(訂閱方法會被主動呼叫,並將事件傳遞過來)。
使用
//倒入gradle 依賴
implementation 'org.greenrobot:eventbus:3.3.1'
1:定義事件型別
public static class MessageEvent { /* Additional fields if needed */ }
2:在需要訂閱事件的模組中註冊EventBus,頁面銷燬時注意登出
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
3:註冊需要接受的事件型別 //注意同一種事件型別不能重複註冊。不然會崩潰,且訂閱方法必須是public型別的。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
// Do something
}
4.傳送事件
EventBus.getDefault().post(new MessageEvent());
這是步驟3種的方法就會收到MessageEvent事件的回撥
原始碼分析
EventBus 主類中只有不到600行程式碼,非常精簡。EventBus使用了對外提供了單例模型,內部構建使用了Build模式。
1 register
public void register(Object subscriber) {
if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
// Crash if the user (developer) has not imported the Android compatibility library.
throw new RuntimeException("It looks like you are using EventBus on Android, " +
"make sure to add the \"eventbus\" Android library to your dependencies.");
}
//1從這裡開始看,獲取呼叫者的類物件。
Class<?> subscriberClass = subscriber.getClass();
//2 找到訂閱類中的訂閱方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
//3遍歷訂閱方法,將訂閱者和其中的訂閱方法繫結。
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
Step1
看下步驟2中的subscriberMethodFinder的findSubscriberMethods方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//1首先從快取map中拿到訂閱類的訂閱方法列表,使用了快取提高效能,nice,不出所料METHOD_CACHE的型別是Map<Class<?>, List<SubscriberMethod> >
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
//2 如果不為空,說明之前該類曾經註冊過,該類的新物件不必重新做繫結了,因為此時的操作是類層面的
if (subscriberMethods != null) {
return subscriberMethods;
}
//如果subscriberMethods 為null,說明該類是第一次註冊,需要將其中的接收方法儲存起來,
//ignoreGeneratedIndex 預設為false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//如果subscriberMethods為null,說明當前類物件沒有生命訂閱方法,丟擲異常
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
//將當前註冊類和其中的註冊方法儲存起來
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
Step2
從步驟2中找出類的註冊方法列表,然後遍歷列表,呼叫下面的方法,將類物件和註冊方法繫結。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//1 找到訂閱方法的事件型別,即傳送事件的MessageEvent.class
Class<?> eventType = subscriberMethod.eventType;
//2 將訂閱者類物件和訂閱事件繫結成一個物件
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//subscriptionsByEventType 這個集合肯定是用來放置同一事件型別的訂閱集合的,因為一個事件可能會有多個訂閱的。
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//如果一個訂閱者多次訂閱了一個事件(@Subscribe註解的方法的引數是同一型別),丟擲異常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//3 按照訂閱方法中@Subscribe中的priority引數進行排序,預設為最低優先順序0。subscriptions種的物件按優先順序排序,收到事件後就會 按優先順序進行回撥
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;
}
}
// typesBySubscriber型別Map<Object, List<Class<?>>>,Key 為訂閱者,value為訂閱者中的訂閱方法,用來記錄每個訂閱者內部都訂閱了哪些事件型別
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//粘性事件相關
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);
}
}
}
2 post
/** Posts the given event to the event bus. */
public void post(Object event) {
//1首先獲取當前執行緒的工作狀態
PostingThreadState postingState = currentPostingThreadState.get();
//2獲取當前執行緒的任務佇列
List<Object> eventQueue = postingState.eventQueue;
//3 將事件加入到事件佇列
eventQueue.add(event);
//4 如果當前執行緒的工作狀態沒有正在傳送事件
if (!postingState.isPosting) {
//標記postingState 的是否是主執行緒,並將工作狀態isPosting 設為true
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
//遍歷任務佇列,傳送事件
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
/**
* 傳送事件
* @param event 事件
* @param postingState 當前執行緒相關的配置
*/
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//如果使用繼承事件的父類/介面,比如你傳送了MessageEvent 事件,如果該事件繼承了BaseEvent和Ievent介面,那麼當你傳送 MessageEvent 事件時,系統也會傳送BaseEvent和Ieven事件
if (eventInheritance) {
//遍歷父類,將事件的父類/介面統統加入到eventTypes中
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
//遍歷eventTypes,依次傳送呼叫事件
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//不實用事件繼承模型,直接傳送該事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
//如果該事件沒有訂閱者丟擲異常
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
//EventBus 內部也適用EventBus 傳送了一個異常事件
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
postSingleEventForEventType
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
//1獲取該事件的所有訂閱關係列表
subscriptions = subscriptionsByEventType.get(eventClass);
}
//2 遍歷訂閱關係列表,依次將事件傳送到訂閱者
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
//將事件傳送到訂閱者
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
postToSubscription
這裡就比較關鍵了,最終到了事件分發的地方了。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//1 首先判斷訂閱關係中訂閱方法的執行緒,就是宣告執行緒時使用@Subcribe註解時傳入的threadMode欄位的值
switch (subscription.subscriberMethod.threadMode) {
case POSTING: //直接傳送事件
invokeSubscriber(subscription, event);
break;
case MAIN: //在主執行緒相應事件
//事件發出執行緒是否是主執行緒
if (isMainThread) { 是,直接傳送
invokeSubscriber(subscription, event);
} else {不是通過mainThreadPoster傳送
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND: //在後臺執行緒相應事件
//事件發出執行緒是否是主執行緒
if (isMainThread) {主執行緒傳送事件,backgroundPoster轉發
backgroundPoster.enqueue(subscription, event);
} else { 非主執行緒傳送事件,直接傳送
invokeSubscriber(subscription, event);
}
break;
case ASYNC: //非同步執行緒相應事件
//通過asyncPoster傳送事件
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
invokeSubscriber
void invokeSubscriber(Subscription subscription, Object event) {
try {
//非常暴力,直接通過回撥呼叫訂閱者中的訂閱方法
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
在postToSubscription中有三個重要的角色mainThreadPoster,backgroundPoster,asyncPoster
其中mainThreadPoster的型別是HandlerPoster。其實就是Handler。呼叫其enqueu()方法
而backgroundPoster和asyncPoster 本質都是Runnable
3 unregister
解綁方法就簡單多了,重要的是就把register裡提到的2個重要的幾何中刪除訂閱者
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
//1 從typesBySubscriber找到訂閱者所訂閱的事件型別列表
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
//2 遍歷列表,依次解綁訂閱者和事件型別。應該是從post分析裡的訂閱事件集合subscriptionsByEventType裡移除對應事件型別的該訂閱者
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
//3移除訂閱者
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
/**
* 解綁訂閱者和事件型別
* @param subscriber 訂閱者
* @param eventType 訂閱的事件型別
*/
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
//從subscriptionsByEventType裡獲取該訂閱事件的訂閱者集合。
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
//遍歷集合,獲取所有的訂閱關係
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
//重要。不然會丟擲ConcurrentModifyException
i--;
size--;
}
}
}
}
4 Subscribe註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* If true, delivers the most recent sticky event (posted with
* {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
*/
boolean sticky() default false;
/** Subscriber priority to influence the order of event delivery.
* Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
* others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
* delivery among subscribers with different {@link ThreadMode}s! */
int priority() default 0;
}
5 ThreadMode
//定義事件回撥方法工作執行緒的類
public enum ThreadMode {
/**
*直接在傳送事件的執行緒裡呼叫Subscriber,這個是預設的設定,事件交付開銷最小,因為它避免了執行緒切換。因此它是那種很快完成的單任務 *預設的的執行緒工作模型。使用該模型的事件必須很快完成,因為當釋出執行緒是主執行緒時,它可能阻塞主執行緒。
/
POSTING,
/**
* 在Android平臺,訂閱者將會在Android的主執行緒呼叫。如果釋出執行緒時主執行緒,訂閱方法將會被直接呼叫。進而阻賽釋出執行緒,如果釋出線 * 程不是主執行緒。事件將會排隊等待分發。使用這種模式的訂閱者必須快速完成任務,避免阻賽主執行緒。非Android平臺和Posting一樣
*/
MAIN,
/**
* 在Android平臺,訂閱者將會在Android的主執行緒呼叫。不同於MAIN,事件將會有序分發。確保了post呼叫時非阻賽的。
*/
MAIN_ORDERED,
/**
* 在Android平臺,訂閱者將會在後臺執行緒被呼叫,如果釋出執行緒不是主執行緒,訂閱者將會被直接呼叫,如果釋出執行緒時主執行緒,那麼EventBus * 使用後臺執行緒,進而有序分發所有事件,使用此模式的Subscribers應該快速完成任務以免阻賽後臺執行緒。非Android平臺,總是用後臺執行緒 * 相應事件
*/
BACKGROUND,
/**
* 訂閱者將會在單獨的執行緒被呼叫,總是獨立於釋出執行緒和主執行緒。釋出事件從不會等待使用這種模式的訂閱方法。如果訂閱方法執行耗時任務, * 則應該使用此模式。比如;網路訪問。避免同時觸發大量的長時間執行的非同步訂閱方法,從而限制併發的執行緒數量。EventBus 使用執行緒池來 * 高效的服用已完成非同步訂閱通知的執行緒
*/
ASYNC
}
6 EventBus2.0和EventBus3.0的區別?
這是在面試過程中,面試官最常問的一個問題。
EventBus2.0和3.0最大的區別有兩點:
1.EventBus2.0中我們在書寫訂閱方法時的名字必須是onEvent開頭,然後通過命名不同來區別不同的執行緒模式。例如對應posting則命名為onEvent(),onEventMainThread()則對應main等。而3.0則可以用任何名字作為方法名稱,只需要在方法名的前面用@Subscribe註解來進行註釋,然後使用threadMode來設定在哪裡執行緒中接收事件和處理事件
2.EventBus2.0使用的是反射的方式來查詢所有的訂閱方法,而3.0則是在編譯時通過註解處理器的方式來查詢所有的訂閱方法。效能上來說,3.0比2.0要高的多。