EvenBus原始碼分析
第一次在平臺寫部落格,之前只是在公眾號上寫,雖然沒人看但是自娛自樂的堅持了一段時間。想想畢竟自己也翻過資料看過別人的部落格才寫出來的應該也有一些價值,不管有沒有人看也應該寫一寫為開發社群貢獻一點見解,畢竟開源的目的不是汲取而是分享。
正文
1、基本使用
- 自定義一個事件類,用於傳遞資料
public static class MessageEvent { /* Additional fields if needed */ } 複製程式碼
- 在需要訂閱事件的地方註冊事件
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } 複製程式碼
- 傳送事件
//普通事件,註冊後傳送才可以接收 EventBus.getDefault().post(new MessageEvent()); //黏性事件,傳送後才註冊也可以接收。訂閱者註解中需要新增sticky=true EventBus.getDefault().postSticky(new MessageEvent()); 複製程式碼
- 處理事件
** * threadMode型別有四種: * POSTING:預設,從什麼執行緒發出就在什麼執行緒接收 * MAIN:事件處理在主執行緒執行,時間太長會導致ANR * BACKGROUNP:在主執行緒發出在主執行緒接收、在子執行緒發出在原執行緒接收 * ASYNC:無論從哪裡發出,都將在子執行緒接收。禁止ui操作 * * @Subscrbe:訂閱者註解 */ @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { /* Do something */ }; 複製程式碼
2、原始碼分析
-
註冊分析

根據原始碼分析後畫的圖。其實關於EventBus的原始碼分析的部落格有很多,這只是方便自己日後看回來更好回憶
- getDefault()
/** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { EventBus instance = defaultInstance; if (instance == null) { synchronized (EventBus.class) { instance = EventBus.defaultInstance; if (instance == null) { instance = EventBus.defaultInstance = new EventBus(); } } } return instance; } 複製程式碼
可以發現,只是一個簡單的單例模式,那麼看看構造方法做了什麼操作。
- 構造方法
public EventBus() { this(DEFAULT_BUILDER); } EventBus(EventBusBuilder builder) { logger = builder.getLogger(); subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>(); stickyEvents = new ConcurrentHashMap<>(); mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; } 複製程式碼
很明顯構造方法只是進行了需要用到物件的初始化,其中EventBusBuilder是一個建造者模式的建造者。
- register(Object)
/** * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they * are no longer interested in receiving events. * <p/> * Subscribers have event handling methods that must be annotated by {@link Subscribe}. * The {@link Subscribe} annotation also allows configuration like {@link * ThreadMode} and priority. */ public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } 複製程式碼
會發現兩個核心的方法
findSubscriberMethods subscribe還有一個核心的類
SubscribeMethod- SubscribeMethod
/** Used internally by EventBus and generated subscriber indexes. */ public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class<?> eventType; final int priority; final boolean sticky; /** Used for efficient comparison */ String methodString; public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) { this.method = method; this.threadMode = threadMode; this.eventType = eventType; this.priority = priority; this.sticky = sticky; } @Override public boolean equals(Object other) { if (other == this) { return true; } else if (other instanceof SubscriberMethod) { checkMethodString(); SubscriberMethod otherSubscriberMethod = (SubscriberMethod)other; otherSubscriberMethod.checkMethodString(); // Don't use method.equals because of http://code.google.com/p/android/issues/detail?id=7811#c6 return methodString.equals(otherSubscriberMethod.methodString); } else { return false; } } private synchronized void checkMethodString() { if (methodString == null) { // Method.toString has more overhead, just take relevant parts of the method StringBuilder builder = new StringBuilder(64); builder.append(method.getDeclaringClass().getName()); builder.append('#').append(method.getName()); builder.append('(').append(eventType.getName()); methodString = builder.toString(); } } @Override public int hashCode() { return method.hashCode(); } } 複製程式碼
發現SubscribeMethod就是一個封裝了訂閱者的執行緒優先順序、黏性事件、訂閱的執行緒模式、訂閱者方法等等資訊的類
- findSubscriberMethods
顧名思義,就是查詢SubscribeMethods的方法。具體看看怎麼實現的
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } if (ignoreGeneratedIndex) { subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } 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; } } 複製程式碼
METHOD_CACHE是一個map集合,用於快取。程式碼中可以看到如果讀取到有快取,會立即返回。如果沒有快取最後也會新增到快取中。
ignoreGeneratedIndex是一個boolean變數,預設值為false。用於判斷是否忽略註解生成的MyEventBusIndex,因為我們通常都是使用單例獲取Evenbus例項,所以不會有MyEventBusIndex。而如果使用的是以下方式
EventBus.builder().addIndex(new MyEventBusIndex()).build() 複製程式碼
會生成一個MyEventBusIndex檔案。由於很少用到我也不是很懂,並且這只是另外一種使用方式而已,不會影響對核心原始碼的理解。所以不做過多講解。因此ignoreGeneratedIndex這個值一般都為false。因此先看看findUsingInfo這個方法
-
- findUsingInfo
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); } 複製程式碼
會發現有個比較經常出現的類FindState。這是一個內部類。封裝了定義訂閱者的公共方法。結構如下:
-
-
- FindState
-
static class FindState { final List<SubscriberMethod> subscriberMethods = new ArrayList<>(); final Map<Class, Object> anyMethodByEventType = new HashMap<>(); final Map<String, Class> subscriberClassByMethodKey = new HashMap<>(); final StringBuilder methodKeyBuilder = new StringBuilder(128); Class<?> subscriberClass; Class<?> clazz; boolean skipSuperClasses; SubscriberInfo subscriberInfo; void initForSubscriber(Class<?> subscriberClass) { ... } boolean checkAdd(Method method, Class<?> eventType) { ... } private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) { ... } void moveToSuperclass() { ... } } 複製程式碼
這種面向物件的封裝思想其實在自定義控制元件中如果對於一些變數有計算的我們同樣可以定義一個內部類做封裝,這樣的顯得程式碼更有可讀性。getSubscriberInfo方法是獲取訂閱者資訊的,它的返回結果會影響後面if-else執行。通過原始碼發現getSubscriberInfo裡面有三種返回結果
-
-
- getSubscriberInfo
-
private SubscriberInfo getSubscriberInfo(FindState findState) { if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo(); if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; } } if (subscriberInfoIndexes != null) { for (SubscriberInfoIndex index : subscriberInfoIndexes) { SubscriberInfo info = index.getSubscriberInfo(findState.clazz); if (info != null) { return info; } } } return null; } 複製程式碼
返回superclassinfo和info,都表示有在EventBusBuilder裡面有配置MyEventBusIndex,正常不會走這裡。所以先看看返回null的情況,這種情況會執行findUsingReflectionInSingleClass方法。
-
-
- findUsingReflectionInSingleClass
-
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } } 複製程式碼
會發現這裡是通過反射獲取到訂閱者的所有方法和屬性儲存到FindState中。這裡提一下的是,反射是寫架構常常用到的知識點,有人說反射是java語言的靈魂。
因為我們一開始的目的是找到SubscribeMethod類,這個類的作用上面說了是一個封裝訂閱者資訊的類,而這時我們已經通過反射拿到了訂閱者的所有方法和屬性。再到後面的新增快取和讀取快取其實只是為了優化效能用到的了,所以到這裡查詢訂閱者的訂閱方法算是完成了。
複製程式碼
看回一開始的註冊方法,第二個關鍵方法是訂閱subscribe
- subscribe
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); 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); } } 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; } } 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); } } } 複製程式碼
Subscription(訂閱物件)是根據Subscriber(訂閱者)和SubscribeMethod(訂閱方法)封裝的一個物件。
subscriptionByEventType是一個map物件,根據eventtype(事件型別)獲取Subscribptions集合,如果為空重新建立並根據eventtype再儲存到map中。
往下的for迴圈是根據訂閱方法的優先順序插入訂閱物件的集合中,最後的subscribeMethod.sticky判斷是否是黏性事件。 會發現未處理的黏性事件(先發送後註冊)其實是在註冊方法裡遍歷取出處理的,而黏性事件的Set集合是由postSticky時儲存的。
- 簡單總結
訂閱方法做了兩件事情,第一是Subscription根據eventType封裝到subscriptionByEventType中,將SubscribeEvents根據Subscriber封裝到 typesBySubscribe中,第二是黏性事件的處理。
複製程式碼
-
事件傳送分析

- post(Object)
/** Posts the given event to the event bus. */ public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { 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; } } } 複製程式碼
首先從PostingThredState中取出事件佇列集合並將當前的事件插入到集合中。最後迴圈交給postSingleEvent處理
- postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); 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); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } } 複製程式碼
eventInheritance表示是否向上查詢事件的父類,可以通過EventbusBuilder進行配置,預設值為true。這時會通過lookupAllEventType查詢父類父類事件並交給postSingleEventForEventType逐一處理。
- postSingleEventForEventType
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; 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; 複製程式碼
postSingleEventForEventType會同步取出訂閱物件。傳遞給PostingThreadState,PostingThreadState其實也只是一個變數的封裝。交給PostingThreadState後再把事件交給postToSubscription處理
- postToSubscription
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { 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.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } } 複製程式碼
這個方法的作用是取出訂閱方法的執行緒模式,然後根據不同的執行緒模式做不同的處理。舉例MAIN,如果是主執行緒則通過反射直接執行訂閱方法,而如果不是主執行緒則需要通過mainThreadPost新增到主執行緒佇列中。mainThreadPost是HandlePost型別繼承自Handle。通過Handle對訂閱方法的執行緒進行切換。
- 簡單總結
所以post方法經過一輪轉換最後就是通過訂閱方法找到執行緒模式,然後經過執行緒排程後再通過反射執行到對應的訂閱方法。
- 心得
最後的反註冊方法毫無疑問就是去對應的容器中去除訂閱方法這裡不做過多講解,看到的朋友覺得寫的不好的地方歡迎指正,如果覺得還可以的朋友可以關注我的公眾號,但寫作對我來說純屬只是給自己一個時間靜靜學習的機會,即便沒有人認可我也會堅持。
