EventBus高效使用及源碼解析
引言
EventBus是greenrobot發布的一個用於事件訂閱和發布的框架,其最大的貢獻在於將事件的訂閱和發布很好地解耦,使代碼更優雅,邏輯更清晰。
EventBus的主要特點如下:- 組件解耦
- 解耦事件訂閱和發布者
- 在Activitives,Fragments和後台線程的使用中表現良好
- 避免了複雜且易導致錯誤的依賴和生命周期問題
- 簡化代碼
- 足夠快
- 輕量(大約50K)
- 已經在100,000,000+個應用上得到了證明
- 具有一些高級特色,如負責傳遞的線程,訂閱優先順序,粘性事件等.
下面就讓我們一起揭開EventBus的神秘面紗。
1.EventBusAnnotationProcessor:EventBus的正確打開方式
網上有很多介紹EventBus的文章,但是幾乎沒有提到EventBusAnnotationProcessor的。實際上,從EventBus 3開始引入了註解,它的主要作用在於使用註解而非反射來解析訂閱信息,並且這個過程是在編譯時而非運行時完成的,因而可使EventBus中的事件訂閱節約很多時間。
要使用EventBus 3的這個新特性,需要以下幾步:
- 添加依賴
+ 由於註解依賴android-apt-plugin,故需要在項目的gradle的dependencies中引入apt,如下:compile org.greenrobot:eventbus:3.0.0
+ 在app module的build.gradle中應用apt插件,並設置apt生成的索引的包名和類名,如果不設置的話在編譯時會報錯。classpath com.neenbedankt.gradle.plugins:android-apt:1.8
+ 最後,需要在app module的dependencies中引入EventBusAnnotationProcessor:apply plugin: com.neenbedankt.android-apt apt { arguments { eventBusIndex "wang.imallen.eventbusannotationsample.MyEventBusIndex" } }
apt org.greenrobot:eventbus-annotation-processor:3.0.1
完成以上幾步後,重新編譯一次,即可在app/build/generated/source/apt/debug/下看到生成的MyEventBusIndex類,如下是我的示例中生成的代碼:
需要注意的是,項目中至少要有一個訂閱信息,否則EventBusProcessor獲取到的訂閱信息為空,自然不生成相應的類了。/** This class is generated by EventBus, do not edit. */public class MyEventBusIndex implements SubscriberInfoIndex { private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(ThirdActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onStickyEvent", wang.imallen.eventbusannotationsample.bean.UserInfo.class, ThreadMode.MAIN, 0, true), })); putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEvent", wang.imallen.eventbusannotationsample.simple.Event.class, ThreadMode.MAIN), new SubscriberMethodInfo("onEventPosting", wang.imallen.eventbusannotationsample.threadmode.SecondEvent.class), new SubscriberMethodInfo("onEventBackground", wang.imallen.eventbusannotationsample.threadmode.ThirdEvent.class, ThreadMode.BACKGROUND), new SubscriberMethodInfo("onEventAsync", wang.imallen.eventbusannotationsample.threadmode.FourthEvent.class, ThreadMode.ASYNC), })); } private static void putIndex(SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } @Override public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info != null) { return info; } else { return null; } }}
重新編譯之後,在第一次使用EventBus之前(如Application或SplashActivity中),添加如下代碼,以使Index生效:
至於EventBus中常規事件和sticky事件的發布和訂閱,都是非常簡單的事情,也不是本文的重點,故不再贅述,就有一點需要注意,EventBus 3中sticky events的訂閱是在註解中添加類似@Subscriber(sticky=true,threadMode=ThreadMode.MAIN)的屬性.EventBus eventBus=EventBus.builder().addIndex(new MyEventBusIndex()).build();
讀者可以fork我在github中的這個示例進行了解: EventBusProcessor使用示例
需要注意的是,如果不利用EventBusAnnotationProcessor,則EventBus 3的解析速度反而會比之前版本更慢。如下是square發布的EventBus3與之前版本在Nexus One,Nexus 5,Nexus 9上的表現對比:
2.EventBus 3.0.0架構
EventBus 3.0.0的發布和訂閱事件的架構如下:
仍然是在某個線程發布後,通過EventBus分發到不同線程中的Subscriber,這一點與之前版本的並無不同。
EventBus 3.0.0的UML圖如下:
需要重點關注的類有EventBus,SubscriberMethodFinder,FindState,SubscriberInfo,PostingThreadState,HandlerPoster,BackgroundPoster,
AsyncPoster,PendingPost,Subscription,SubscriberMethod,後面的分析也主要與這些類有關.
3.事件訂閱/解除訂閱
下面的代碼講解將結合 EventBusProcessor使用示例 進行。
事件訂閱的代碼如下:
public void register(Object subscriber) { //subscriberClass是類似wang.imallen.eventbusannotationsample.MainActivity這樣的 Class<?> subscriberClass = subscriber.getClass(); //subscriberMethods是類似MainActivity中的onEvent(),onEventAsync(),onEventBackground(),onEventMain(),onEventPosting(),onStickyEvent()這樣的 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //ignoreGeneratedIndex默認為false,因為反射成本高 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; } }
要證實這一點很簡單,進入findUsingReflection()方法看一下即可:
知乎專欄 - 隨心寫作,自由表達3.1 使用反射獲取訂閱信息
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findUsingReflectionInSingleClass(findState); findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //methods是所有聲明的方法,不包括只在基類的方法,如MainActivity中就是onCreate(),onDestroy(),onEvent(),onEventAsync(),onEventBackground(),onEventMain(),onEventPosting(),onStickEvent(),openSecondActivity() 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(); //必須是public和非static,非abstract,非bridge,非synthetic的方法 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) { //evnetType是類似wang.imallen.eventbusample.simple.Event這樣的事件類型 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"); } } }
3.2 使用EventBusAnnotationProcessor解析結果獲取訂閱信息
回到findSubscriberMethods()這個方法,ignoreGeneratedIndex為false時,通過findUsingInfo()方法來獲取訂閱信息:
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); //使用while而不是if的原因是findState.moveToSuperclass()會切換findState.clazz對象為基類Class對象,從而循環解析 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 { //如果發現獲取不到subscriberInfo的話,就還是要使用反射來獲取 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
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; }
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
回到findUsingInfo()中,仍然是要檢查是否已添加同類型事件的回調,如果沒有添加才會添加到findState.subscriberMethods中.
需要注意的是else中findUsingReflectionInSingleClass(findState),我個人認為是冗餘代碼,除非EventBusAnnotationProcessor解析出錯或不完整,某種程度上是對EventBusAnnotationProcessor不自信的表現,期待作者在後面去掉吧。
注意return語句之前的findState.moveToSuperclass()這句,它其實是將findState中的clazz對象換成基類的,也就是說,事件訂閱是可以繼承的,之後循環之前的過程。
3.3 綁定訂閱對象和方法
回到EventBus#register()中,在獲取subscriberMethods之後,就是遍歷各訂閱方法,逐個綁定。subscribe()的代碼如下:
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //eventType是類似wang.imallen.eventbusannotationsample.simpel.Event這樣的 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(); //注意這裡是i<=size,是為了考慮到subscriptions為空的情況也能添加newSubscription到subscriptions中 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<>(); //subscriber是類似MainActivity,而subscribedEvents是類似MainActivity中涉及到的所有eventType,如Event.class,FirstEvent.class,SecondEvent.class,...,UserInfo.class typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) { //eventInheritance表示是否要考慮繼承 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(); //給這個新的訂閱者發送所有eventType類型或其子類的事件 checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { //如果不考慮繼承,則只要給新的訂閱者發送eventType這個類型的最近一次的粘性事件 Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
知乎專欄 - 隨心寫作,自由表達
- 首先根據subscriber和subscriberMethod生成一個Subscription對象
- 之後從subscriptionsByEventType這個Map中獲取對應eventType的所有Subscription對象,如果為空則新建,否則判斷是否已經包含newSubscription,如包含則拋出已訂閱的異常
- 遍歷subscriptions,如果發現subscriberMethod的優先順序比某個Subscription對象中的優先順序高,則用當前的Subscription對象取代它,之後跳出循環
- typesBySubscriber是以訂閱對象為key,事件類型列表為value的Map,將當前的事件類型添加到對應的List中即可
- 最後一部分比較特殊,如果訂閱的是粘性事件,則要分兩種情況:
- 如果要考慮事件繼承(EventBusBuilder中默認為true),則需要遍歷stickyEvents這個以事件的Class為key,事件本身為value的Map,如果發現某個事件類型是eventType的子類(就是eventType.isAssignableFrom(candidateEventType)的含義),則要發布這個事件類型對應的事件(也就是該事件類型最近一次事件)給這個訂閱方法。至於事件發布的代碼,在第4節會分析。
- 如果不考慮事件繼承,則只需要將eventType最近一次的事件(如果有的話)發送給這個訂閱方法即可
3.4 解除訂閱
解除訂閱的代碼如下:
/** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
代碼很簡單,就是解除與該訂閱對象關聯的所有事件類型.unsubscribeByEventType()的代碼如下:
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */ private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { 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); i--; size--; } } } }
解除訂閱的邏輯很簡單,就不畫流程圖了.
知乎專欄 - 隨心寫作,自由表達3.5 事件訂閱總結
至此,事件訂閱就基本分析完了,我們可以從中梳理出如下流程:
其中紅線標出的是我認為不合理的地方。
4.事件發布
4.1 發布普通事件
post()的代碼如下:
/** 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 = Looper.getMainLooper() == Looper.myLooper(); 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; } } }
- 首先獲取當前線程的PostingThreadState對象,實現方式是ThreadLocal
- 然後將當前event添加到postingState的事件隊列中
- 如果當前線程沒有正在發布事件,則遍歷當前線程的事件隊列,將事件逐個發布出去,其中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) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */ private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) { synchronized (eventTypesCache) { List<Class<?>> eventTypes = eventTypesCache.get(eventClass); if (eventTypes == null) { eventTypes = new ArrayList<>(); Class<?> clazz = eventClass; while (clazz != null) { eventTypes.add(clazz); addInterfaces(eventTypes, clazz.getInterfaces()); clazz = clazz.getSuperclass(); } eventTypesCache.put(eventClass, eventTypes); } return eventTypes; } }
再回到postSingEvent()中,下面就是遍歷eventTypes了,進入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; }
如果eventClass對應的訂閱信息不為空,則將當前事件逐個發布到各訂閱對象中,真正的發布處理在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 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); } }
- 如果是POSTING模式,則直接回調事件訂閱方法,說明POSTING模式代表發布和訂閱回是在同一個線程;
- 如果要在主線程回調,而發布線程就是主線程的話,則直接回調;否則需要利用主線程發布代理(mainThreadPoster)來進行發布;
- 如果要在後台線程中回調,而發布線程是主線程的話,則需要利用後台線程發布代理(backgroundPoster)來進行發布,否則直接發布;
- 如果是非同步發布,則不管當前是在什麼線程,都是利用非同步發布代理(asyncPoster)來進行發布
- 如果threadMode不是其中之一的話,則拋出異常.
到這裡為止,普通事件的發布就基本梳理完了。
4.2 發布sticky event
粘性事件的發布代碼如下:
/** * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky * event of an events type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}. */ 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); }
至此,我們可以梳理出發布事件的流程:
推薦閱讀:
※Android ConstraintLayout使用指南
※Push mail 的實現原理是什麼?
※如果只按性能配置來講,oppo R11應該值多少錢?同等性能、配置的其他手機大家有推薦的嗎?
※如何評價諾基亞首部安卓手機-諾基亞X?