EventBus源碼閱讀記錄
EventBus是一個Android上用的消息分發(fā)的類庫,非常靈活好用,主要的原理是利用了反射注冊以及調(diào)用.
本文是在閱讀EventBus的源碼過程中所記錄的東西, 遇到不懂的去查了,然后留下了鏈接.
有點(diǎn)流水賬,講得也不是很深入,如果有錯請幫忙指正.
repo地址:
greenrobot/EventBus
EventBus的構(gòu)造
雙重加鎖的單例.
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
但是仍然開放了構(gòu)造函數(shù),用于構(gòu)造其他別的對象.
Builder模式: EventBusBuilder
.
有一個DEFAULT_BUILDER
.
注冊
注冊即添加訂閱者,調(diào)用register()
方法:
方法參數(shù)最全時共有三個參數(shù):
private synchronized void register(Object subscriber, boolean sticky, int priority) {
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
其中subscriber
(訂閱者)傳入的是一個對象,用到了它的class.
SubscriberMethodFinder
會去找這個類中的方法.
被找到的方法最后會被緩存到一個map里,key是class
, value是ArrayList()
.
尋找方法
在一個類(class)中尋找方法的過程, 首先是拿出方法:
在循環(huán)中skip了一些系統(tǒng)的類, 因?yàn)槲覀儾豢赡茉谶@些類里加入方法.
while (clazz != null) {
String name = clazz.getName();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
break;
}
// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
try {
// This is faster than getMethods, especially when subscribers a fat classes like Activities
Method[] methods = clazz.getDeclaredMethods();
filterSubscriberMethods(subscriberMethods, eventTypesFound, methodKeyBuilder, methods);
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
Method[] methods = subscriberClass.getMethods();
subscriberMethods.clear();
eventTypesFound.clear();
filterSubscriberMethods(subscriberMethods, eventTypesFound, methodKeyBuilder, methods);
break;
}
clazz = clazz.getSuperclass();
}
反射
關(guān)于反射的性能討論, 代碼中有說:
// This is faster than getMethods, especially when subscribers a fat classes like Activities
Method[] methods = clazz.getDeclaredMethods();
為什么呢?
getMethods()
返回了所有的public方法,包含從所有基類繼承的,也即包含了從Object類中繼承的public方法.
getDeclaredMethods()
返回了該類中聲明的所有方法,包括各種訪問級別的,但是只包含本類中的,不包括基類中的方法.
相關(guān)DOC:
反射package-summary
getDeclaredMethods()
getMethods()
Issue of NoClassDefFoundError
這里有一個try catch主要是為了解決這個issue:
本來的流程是:
- 從自己的class開始,每次都
getDeclaredMethods()
, 即提取自己類中的方法,不取基類. - 取完之后,
getSuperclass()
,獲取基類的class,重新進(jìn)入while循環(huán).直到進(jìn)入java包或者android包才退出.
但是getDeclaredMethods()
會檢查一些參數(shù)和返回值, 如果找不到類型則拋出NoClassDefFoundError.
getMethods()
卻不檢查.
什么樣的情況會拋出這個Error呢?
Android代碼里可能會有一些方法標(biāo)明了@TargetApi
,表明是更高級的sdk上才會有的.
這樣在低版本的機(jī)器上遇到了這些代碼,就無法解析出它們的類了.
只要你的作為subscriber的class里含有這種東西,就會出現(xiàn)問題.
為了解決這個崩潰, 所以代碼里catch了一把,然后采用第二種方案getMethods()
,一次性get所有基類中的方法,這種效率雖然低,但是不會拋異常.
需要把之前的map都清理一把.
篩選方法
得到了所有的方法之后,開始篩選方法:
private void filterSubscriberMethods(List subscriberMethods, HashMap eventTypesFound,
StringBuilder methodKeyBuilder, Method[] methods)
這里第一個參數(shù)會作為最后的返回值,即我們方法選擇的結(jié)果.
篩選的過程, 遍歷所有找到的方法:
- 看它是以”onEvent”開頭,即為我們要找的目標(biāo)方法.
- 然后
getModifiers()
看它是一個public的方法,并且不是我們要忽略的方法.
注意這里用到了位操作&來比較. 結(jié)果不為零表示滿足,為零表示不滿足.
默認(rèn)的忽略方法是static, bridge, synthetic方法.
后兩個詞指的其實(shí)是同一種東東,但是這是什么東東呢?
是編譯器生成的方法, 見參考鏈接:
從上面的例子中可以看出,編譯器生成bridge方法主要是為了保證多態(tài)的順利進(jìn)行.它和基類的簽名一樣,但是實(shí)現(xiàn)去調(diào)用了子類的方法.自己偷偷完成了其中的類型轉(zhuǎn)換.
獲取參數(shù)類型:必須是一個參數(shù).
獲取
ThreadMode
: 即看方法名中onEvent之后還是什么,一共有四種Mode,對應(yīng)四種方法名:
onEvent(), onEventMainThread(), onEventBackgroundThread(), onEventAsync()
如果獲取不到ThreadMode
,則continue;即這個方法不是我們要找的方法.用
StringBuilder
組成一個key: method name>parameterType class name.
注意這里StringBuilder的清理方式是setLength(0)
.
然后放進(jìn)了一個eventTypesFound
的HashMap, String是key, Class是value,這里放的是method.getDeclaringClass()
;即方法聲明的那個類的類型.
注意這里還利用了put()
方法的返回值,如果map里之前有這個key對應(yīng)的值,那么老的value會作為返回值返回.
文檔:
HashMap.put()
這里還用了這個一個方法: isAssignableFrom
判斷是否自己的class是參數(shù)的基類或接口.如果傳入的參數(shù)是當(dāng)前對象的子類或自身,則返回true.
如果有old class存在,并且old class和新的class不能互相轉(zhuǎn)換, 后者old是new的子類, 那么eventTypesFound
這個map里還是保存老的值.
如果存在old class,但是old class是新加class的父類,會把新的class加進(jìn)eventTypesFound
的map,取代前者,即這個map中盡量放繼承體系下層中更具體的類.
這里雖然父類沒有被放進(jìn)eventTypesFound
,但是父類的方法仍然會被加進(jìn)最后返回的methods的map.
篩選結(jié)束后,我們就獲取到了所有的目標(biāo)方法.
把它們都存在了一個cache map里面,以免同一個類下次我們又要重新篩選一遍:
private static final Map, List> methodCache = new HashMap, List>();
訂閱
得到了方法的list(List
)之后,我們要對每一個成員調(diào)用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority)
方法.
里面有一個新的數(shù)據(jù)類型CopyOnWriteArrayList
:
CopyOnWriteArrayList Java doc
CopyOnWriteArrayList android doc
類說明: A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.
這個數(shù)據(jù)類型是一個ArrayList,但是它在每次進(jìn)行變異操作之前都拷貝一份新的.它底層的數(shù)組是volatile的.
這種數(shù)據(jù)類型的寫操作代價很高.
subscribe()
方法中主要是給這兩個字段放數(shù)據(jù):
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
key是eventType的Class, value是Subscription這種數(shù)據(jù)類型的數(shù)組:
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
private final Map>> typesBySubscriber;
key是subscriber,即訂閱者的類的對象,value是eventType的class,即事件類.
注銷
unregister()
的時候, 傳入subscriber:
首先從typesBySubscriber獲取到事件的List,然后遍歷這個List, 從subscriptionsByEventType中移除該eventType,并且subscriber是當(dāng)前subscriber的Subscription.
遍歷完成之后,從typesBySubscriber移除該subscriber.
事件觸發(fā)
好了,注冊和反注冊到這里就結(jié)束了,看起來也就是找到一些方法和類型,放在一些map里面,注銷的時候再從map里面拿出來而已.
真正做事情的代碼呢?
首先看事件的觸發(fā): post()
方法, 這里傳入的參數(shù)是事件類對象.
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List 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;
}
}
}
大致地看上去好像就是加入了一個隊(duì)列,然后發(fā)送出去直到隊(duì)列為空.
對每一個事件來說,是調(diào)用了postSingleEvent()
這個方法.
postSingleEvent()
這個方法里eventInheritance
為true時(默認(rèn)行為)會把event的class拿出來,然后取出它的所有基類和接口,和它自己一起放在一個map里.
這是可以理解的,因?yàn)榭赡芪覀儽緛淼男枨笫潜O(jiān)聽了一個災(zāi)難事件,后來的需求發(fā)展,又寫了個它的子類事件叫地震.
那么當(dāng)我post地震事件的時候,除了地震事件后來新加的處理,當(dāng)然也要采取原先災(zāi)難事件的相關(guān)措施.
取出所有基類和接口的方法:lookupAllEventTypes()
/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private List> lookupAllEventTypes(Class eventClass) {
synchronized (eventTypesCache) {
List> 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;
}
}
所有這些費(fèi)時的遍歷查找操作都是有一個map作為cache的.
注意這里添加接口的時候,因?yàn)?strong>接口是多繼承的,所以除了去重以外,還需要深入遍歷:
/** Recurses through super interfaces. */
static void addInterfaces(List> eventTypes, Class[] interfaces) {
for (Class interfaceClass : interfaces) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(interfaceClass);
addInterfaces(eventTypes, interfaceClass.getInterfaces());
}
}
}
獲取到所有類型之后,進(jìn)行遍歷, 對每一個eventClass進(jìn)行處理, 真正的對每一個類型post的方法是這個:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass)
這里,從之前那個subscriptionsByEventType
里面,根據(jù)eventClass把CopyOnWriteArrayList
拿出來.
這里拿出來的就是一個List,里面是一個一個的onEventXXX方法的個體,
對每一個Subscription
,執(zhí)行了:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread)
線程模式
這里根據(jù)線程模式不同,有一個switch case.
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case PostThread:
invokeSubscriber(subscription, event);
break;
case MainThread:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BackgroundThread:
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);
}
}
這里invokeSubscriber(Subscription subscription, Object event)
方法就是直接通過Method
, 反射調(diào)用, invoke了那個方法.
`case PostThread`: 直接在當(dāng)前線程調(diào)用這個方法.
`case MainThread`: 如果當(dāng)前線程是主線程,則直接調(diào)用,否則加入mainThreadPoster的隊(duì)列.
`case BackgroundThread`: 如果當(dāng)前是主線程,加入backgroundPoster隊(duì)列, 否則直接調(diào)用.
`case Async`: 加入asyncPoster隊(duì)列.
加入的三個隊(duì)列類型如下:
private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
HandlerPoster
繼承自Handler, 內(nèi)部有一個PendingPostQueue
.
這三個poster里面都是這個PendingPostQueue
, 數(shù)據(jù)結(jié)構(gòu)是PendingPost
關(guān)于Queue的相關(guān)知識
隊(duì)列Queue: Java中Queue是一個接口, 類文檔:
Queue Java doc
它是繼承自Collection這個接口:
Collection
Queue這個數(shù)據(jù)結(jié)構(gòu)可以自己定義順序, 可以用來做FIFO也可以用來做LIFO.
每一種Queue的實(shí)現(xiàn)都必須指定要用什么順序.
不管是什么順序,head上的那個元素都是remove()
或poll()
即將移除的元素.
offer()
方法將會試圖插入一個元素,如果失敗了就會返回false.
remove()
和poll()
方法都會刪除并返回head元素.
peek()
只查詢,不remove.
主線程處理 HandlerPoster
所以這里看看HandlerPoster
是怎么做的:
- 它繼承自
Handler
, 初始化的時候用的是mainLooper,所以確保了消息處理操作都是在主線程:
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
- 這個里面寫了一個自己的queue: PendingPostQueue里面包含的數(shù)據(jù)是:
PendingPost
.
PendingPost
這個類里用了一個pool來實(shí)現(xiàn)一個對象池,最大限制是10000.
obtain的時候, 如果池子里有對象,則從池子里拿出來一個, 如果池中沒有對象,則new一個新的PendingPost; release的時候放回池子去.
HandlerPoster
主要做兩件事:
- enqueue一個PendingPost, sendMessage,
- 在handleMessage()方法里面處理message.
handleMessage()里面是一個while循環(huán),從隊(duì)列里面拿出PendingPost然后調(diào)用EventBus的invokeSubscriber()方法.
這里調(diào)用方法之前就會release該P(yáng)endingPost.
異步和后臺處理 AsyncPoster和BackgroundPoster
AsyncPoster
和BackgroundPoster
都是一個Runnable
.
enqueue的時候把PendingPost加入隊(duì)列, 然后調(diào)用eventBus.getExecutorService().execute(this);
run()
方法里面就是從隊(duì)列中拿出PendingPost,然后invoke,和上面很像.
默認(rèn)的對象是:
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
提供了一個線程池,可以異步地執(zhí)行操作.
那么它們兩者有什么不同呢?
AsyncPoster很簡單, run里面直接invoke, 沒有過多的判斷. 即對每一個任務(wù)都是直接啟動線程執(zhí)行.
BackgroundPoster比較復(fù)雜,有一個boolean來判斷是否正在run, run()方法里面是一個while true的循環(huán),當(dāng)queue全部被執(zhí)行完之后才return.
如果隊(duì)列中有任務(wù)正在執(zhí)行,這時候enqueue()操作會加入元素到隊(duì)列中,等待執(zhí)行.
即BackgroundPoster只用了一個線程,所有的事件都是按順序執(zhí)行的,等到前面的任務(wù)執(zhí)行完了才會進(jìn)行下一個.
對各個模式的說明可以參見ThreadMode.java
類.
Async模式下,不管你的post thread是什么,都是會新啟線程來執(zhí)行任務(wù)的,所以適用于那些比較耗時的操作.
為了避免并發(fā)線程過多, EventBus里面使用了一個線程池來復(fù)用線程.
事件取消
有一個public的cancel方法:
public void cancelEventDelivery(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
if (!postingState.isPosting) {
throw new EventBusException(
"This method may only be called from inside event handling methods on the posting thread");
} else if (event == null) {
throw new EventBusException("Event may not be null");
} else if (postingState.event != event) {
throw new EventBusException("Only the currently handled event may be aborted");
} else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.PostThread) {
throw new EventBusException(" event handlers may only abort the incoming event");
}
postingState.canceled = true;
}
這個方法的使用可以從測試代碼里面看出來:
1.首先它只能在handler里面調(diào)用, 即第一個異常.這里判斷的isPosting這個值在post的時候變?yōu)閠rue,處理完就變?yōu)閒alse.
這里用到的currentPostingState:
private final ThreadLocal currentPostingThreadState = new ThreadLocal() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
ThreadLocal類是什么?
ThreadLocal類
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
主要是用來給每一個線程保存一個不同的狀態(tài)值.
這個currentPostingThreadState在第一次被調(diào)用get()
方法的時候初始化,也即在public void post(Object event)
方法里.
然后修改了它的狀態(tài), 之后再在同一個線程里,即可訪問到它的狀態(tài).
這里cancel的測試也寫得很有意思,可以看一下.
黏性事件
什么叫Sticky?
字面上看是黏性的.
之前的事件都是非黏性的,即有一個register()
和unregister()
方法.
register()
了subscriber之后, EventBus會掃描該類中的onEventXXX()方法,建立一些map來記錄.
unregister()
即合理地清除了這些數(shù)據(jù).
而對于sticky的事件,注冊時調(diào)用registerSticky()
, 并沒有相應(yīng)的注銷方法.只有一個單獨(dú)的removeAllStickyEvents()
方法.
sticky的事件注冊的時候, subscribe()
方法中, 除了重復(fù)上面正常的過程之外, 還有一個額外的map:
private final Map, Object> stickyEvents;
這個數(shù)據(jù)類型是: stickyEvents = new ConcurrentHashMap, Object>();
存的是event的Class和event對象.
注冊時如果發(fā)現(xiàn)這個map中相同的event type要處理,會立即觸發(fā), 通知到它的訂閱者.
注意這個sticky event存的是最近的一個事件: most recent event.
sticky事件觸發(fā)的時候調(diào)用:
public void postSticky(Object event)
sticky的代碼里有一個cast()
方法:
看文檔:
Class
這個cast()
方法就是用來把對象強(qiáng)轉(zhuǎn)成當(dāng)前的這個Class類型.
結(jié)語
EventBus是一個Android上用的消息分發(fā)的類庫,非常靈活好用,主要的原理是利用了反射注冊以及調(diào)用.
本文是在閱讀EventBus的源碼過程中所記錄的東西, 遇到不懂的去查了, 然后留下了鏈接.
有點(diǎn)流水賬,講得也不是很深入,如果有錯請幫忙指正.