前言
EventBus是安卓(Java中也可以用)開發中非常流行的一個第三方庫,是一種發佈/訂閱事件的總線.擁有以下特性:
* 簡化了組件間的通信,可應用與Activity,Fragment和後臺線程
* 將事件的發送方和接收方解耦
* 體積小
現在的EventBus早已經從2.0進入3.0版本,而且最新版3.1.1也已經穩定了挺長的時間,從17年底再沒有更新過GitHub.因爲之前的項目中集成的都是3.0.0
版本的,因此以該版本源碼做分析.常見的使用方法就不介紹了,官方文檔裏都有.所有操作,包括髮送事件、在訂閱事件的組件中註冊
和解綁,都是通過EventBus.getDefault()
創建一個單例對象,完成後續方法的調用.
register(註冊事件)
先看註冊事件的部分,因爲EventBus通常的使用順序就是接收方註冊->發送方定義發送事件->接收方定義接受入口->發送方發送事件->收到事件。當然從數據流向看先從發送事件開始也是可以理解的:
源碼:
public void register(Object subscriber) {
//註冊事件所在的類
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
其中的subscriberMethodFinder是SubscriberMethodFinder類型對象,在EventBus的默認構造時初始化。通過findSubscriberMethods方法可以找到該類中添加了Subscribe註解的方法。只有添加了該註解的方法(方法名可以任意)纔可以收到事件。如果該類沒有添加過註解,拋異常。
findSubscriberMethods
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<Class<?>, List<SubscriberMethod>>
類型,它存儲的value是該類中所有添加了Subscribe註解的方法(被包裝爲SubscriberMethod類)的集合。該類第一次註冊事件的時候集合爲null,默認ignoreGeneratedIndex爲false,會走到方法findUsingInfo.而在該方法內部,默認的情況下經過一系列邏輯判斷又會走到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()));
}
}
}
}
}
前面的操作已經將該類封裝到了靜態內部類FindState中,作爲clazz字段。這裏重新加工要使用的FindState的步驟是:
* 通過getDeclaredMethods獲取該類本身定義的所有類型的方法.因爲訂閱類通常是Activity這樣的組件,層層繼承,這樣效率更高。
* 遍歷方法數組,獲取方法的修飾符並進行判斷。這也是爲什麼將接受事件的方法用private、static修飾會拋異常的原因
* 符合要求,獲取該方法的參數個數,必須爲1。
* 參數個數爲1,則判斷方法內是否定義了Subscribe註解。若有,則獲取參數的類,也就是發送事件方那邊所定義的事件類型。
* 通過FindState的checkAdd方法判斷,這裏有兩層判斷。校驗通過,會包裝一個SubscriberMethod對象添加到FindState的subscriberMethods集合中。
* 第一層,只是快速簡單的判斷事件類型。FindState在初始化的時候創建了對象anyMethodByEventType,類型爲Map<Class, Object>
。訂閱事件的類如果只定義了一個符合要求的接收事件的方法,那麼以方法接收的事件類爲key存儲該方法。
* 第二層校驗,通常一個訂閱者中不會有多個方法接收同一事件,但如果定義了多個呢?這時anyMethodByEventType的value正是前一個定義的接收事件的方法,會調用checkAddWithMethodSignature方法校驗。源碼如下:
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
methodKeyBuilder是一個StringBuilder對象。可以看出,每次調用該方法都將重置它,並且以方法名+'>'+事件類型
作爲鍵值。subscriberClassByMethodKey是一個Map<String, Class>
對象,存儲的value爲接受事件的方法所在類。只有該Map存在重複鍵時該方法纔會返回false,但是我們定義的鍵值,因爲同一個類中不會出現同名同參數類型同參數個數的方法,所以不會重複,返回true。
額外話:該方法什麼情況會返回false?假設有一個類ActivityA,其中定義了訂閱和接受事件的方法,另外有一個類ActivityB繼承了A作爲頁面展示,並重寫了其中的訂閱方法。這時候在findUsingInfo方法中的while循環體內,會繼續向B的父類A調用。這時的subscriberClassByMethodKey相同的鍵對應的value就不爲空了,並且由於A、B之間 繼承關係,方法會走入else分支返回false。最終拋出IllegalStateException。因此要注意派生關係在訂閱事件的應用
繼續向下,出現了接受同一事件的多個方法,這時anyMethodByEventType的value用任意的對象代替以“消費”該方法,源碼中直接取的FindState的當前對象。接着又一次調用了checkAddWithMethodSignature方法。前面說過了因爲鍵不會重複的原因,因此最終返回true。該接受事件方法校驗通過。也就是說,在同一個類中定義多個接受同一事件的方法,那麼這些方法都會執行。
準備好事件集合
上面的方法調用完畢後就獲取到了訂閱事件方法的集合,並且將其緩存到METHOD_CACHE中,這樣同一個類多次註冊事件時就不必做重複的大量邏輯判斷。這時候就可以遍歷該集合,正式開始事件的訂閱了。
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);
}
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);
//後面分析stick事件
}
將訂閱事件的類和訂閱方法包裝成一個Subscription對象。CopyOnWriteArrayList相當於一個線程安全的ArrayList,其實就是內部實現的方法上加了鎖。subscriptionsByEventType是類型爲Map<Class<?>
, value爲CopyOnWriteArrayList<Subscription>>
的對象,會以事件類型爲鍵存儲所有訂閱該事件的方法集合,根據@SubScribe的priority,優先級高的會添加到頭部,保證了多個訂閱方法的執行順序。typesBySubscriber是類型爲Map<Object, List<Class<?>>>
的對象,用於存儲該訂閱類中訂閱的所有事件類型
post(發送事件)
接收方註冊了事件,訂閱了事件,就來看發送方發佈事件的代碼.最常用的post方法,其中的參數event就是我們在要發送事件的地方自己定義的一個對象,通常是一個靜態內部類:
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;
}
}
}
首先在EventBus第一次被初始化的時候創建了一個ThreadLocal類的對象currentPostingThreadState,其泛型形參爲EventBus中的靜態內部類PostingThreadState.在post的時候會獲取該內部類,從而獲取到其內部的eventQueue(事件隊列,用ArrayList存儲),並且將要發送的事件(抽象爲發送方自己定義的對象)加入到該隊列中.isPosting可以避免併發情況下一個Event的多次被調用.判斷事件不處於發送中的狀態後,開始依次從隊列中的頭部取出事件發送,直到隊列爲空.可以看出,發送事件的同時會將事件移除出隊列.發送單個事件的方法爲postSingleEvent。
postSingleEvent
忽略一些邏輯判斷,核心代碼是:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
postSingleEventForEventType(event, postingState, eventClass);
}
看到這個方postSingleEventForEventType,該方法內部的邏輯是:如果對應事件類型的訂閱方法存在,那麼會爲每個訂閱方法調用postToSubscription。它會比較發送事件和訂閱事件各自所在的線程來決定是否立即執行。符合條件的話,有訂閱類,有事件類型,最終會反射調用Method的invoke方法,也就是我們接受到了事件開始處理了。這樣整個發送->事件流也就結束了。
ThreadMode
EventBus定義的一個枚舉。我們通過訂閱方法上添加的Subscribe註解就可以設置這個值。它代表了訂閱方法在哪類線程被EventBus調用。設計之初就是讓發送和接收事件的線程獨立開來。共定義了四種線程:
POSTING
默認。訂閱事件和發送事件處在同一線程。最常用的是這種,它可以避免切換線程的損耗。但由於發送事件可能在主線程中,因此接收事件的方法不能做耗時操作,否則會阻塞線程。
MAIN
接收事件總會在主線程(UI線程)中,也要避免耗時操作。如果發送事件在子線程,則接收事件會放到MessageQueue中等待執行
BACKGROUND
接收事件總會在子線程中。如果發送事件也在子線程中,那麼接收事件應立即在發送事件所在的線程中執行。如果發送在主線程中,接收事件運行在EventBus維護的單一子線程中,因爲還可能存在其他接收事件的方法,所以也要避免阻塞。
ASYNC
接收事件在一個獨立的線程中執行,獨立於發送方所在線程和主線程。適合做耗時操作,如網絡請求。EventBus內部使用了線程池來複用這些異步的接收事件方法所在線程,但還是要避免同一時間內大量觸發此類型方法。
Poster
前面提到,如果接收和發送所處的線程不一樣,會將事件先放到隊列中等待執行。有這麼三種情況:
發送方子,接收方主
加入到HandlerPoster,這個類繼承了Handler,其實就是在enque方法中調用了sendMessage,又在handleMessage中調用了EventBus的invokeSubscriber方法,完成了接受事件方法的調用。
發送方主,接收方子
加入到BackgroundPoster,這個類實現類Runnable,在enque方法中找到了EventBus全局定義的一個默認的線程池:Executors.newCachedThreadPool(),獲取了一個無數量上限,無核心線程,可以複用空閒線程的線程池。enque方法加鎖,而且全局定義了一個volatile類型變量,保證了任一時間只有一個任務被執行,也就是前面說的單一子線程。在run方法中調用了invokeSubscriber。
接收方獨立
加入到AsyncPoster,它也實現Runnable。但不同於BackgroundPoster,它沒有加鎖和保證有序性,因此等待被執行的任務只要放入線程池就行了。
postSticky
假設有這種場景,我在登錄頁面輸入用戶名和密碼後跳轉到其他的一些界面,相應的頁面都要有所變化。那這時候發送事件的時候因爲新打開的頁面還沒有初始化,還沒有完成訂閱事件,subscriptionsByEventType裏不存在value,也就無法接收到事件。這時候就會用到postSticky.
/**
* 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 event's 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);
}
和普通的發送事件相比,就是同步將要發送的事件類型和事件對象存儲到了stickyEvents中。然後到了訂閱事件時,會走到subsribe方法的該分支,默認精簡如下:
if (subscriberMethod.sticky) {
// 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);
}
}
}
取出之前的stickyEvents,遍歷其鍵值對。校驗發送的和接受的是同一事件後,判斷如果存儲的事件對象不爲空,則調用postToSubscription方法。這樣也就保證了即使發送事件在先,註冊事件在後,也可以接收到事件。注意的是接受事件的方法的Subscribe註解中的sticky要置爲true.
unregister
/** 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());
}
}
取消訂閱的方法中,首先移除了所有訂閱了該事件的方法集合中該類中所定義的方法。然後移除了該類中所有的訂閱事件類型。
總結
EventBus作爲部分替代BroadcastReceiver和接口回調的一款知名框架,本身是基於觀察者模式實現的,實現原理總結了有下面幾個核心要點:
* 單例模式
* 建造者模式
* 包裝類
* 鍵值對存儲
* 使用鎖同步方法
* 基於註解使用
* 通過反射找匹配,大量的反射方法:getClass、getSuperclass、getDeclaredMethods、getParameterTypes、getAnnotation、isAssignableFrom、invoke