EventBus原始碼分析(一):入口函式提綱挈領(2.4版本)
EventBus原始碼分析(一):入口函式提綱挈領(2.4版本)
EventBus原始碼分析(二):register方法儲存事件的訂閱者列表(2.4版本)
EventBus原始碼分析(三):post方法釋出事件【獲取事件的所有訂閱者,反射呼叫訂閱者事件處理方法】(2.4版本)
EventBus原始碼分析(四):執行緒模型分析(2.4版本)
EventBus原始碼解讀詳細註釋(1)register的幕後黑手
EventBus原始碼解讀詳細註釋(2)MainThread執行緒模型分析
EventBus原始碼解讀詳細註釋(3)PostThread、MainThread、BackgroundThread、Async四種執行緒模式的區別
EventBus原始碼解讀詳細註釋(4)register時重新整理的兩個map
EventBus原始碼解讀詳細註釋(5)事件訊息繼承性分析 eventInheritance含義
EventBus原始碼解讀詳細註釋(6)從事件釋出到事件處理,究竟發生了什麼!
本文是EventBus原始碼分析的第一篇文章,不拘泥於具體的實現細節,從巨集觀上把握EventBus的設計思路,而EventBus總體的設計思路為:
EventBus例項儲存了事件到訂閱者列表的map,釋出事件的時候,從該map中取出該事件的所有訂閱者,在規定的執行緒中反射呼叫所有訂閱者的事件處理方法。
DCL單例建立EventBus物件
EventBus提供了靜態方法建立EventBus物件,實現方式為標準的DCL雙檢鎖的單例模式。DCL雙檢鎖分為兩部分:
- 靜態易變的defaultInstance屬性
- 靜態的getDefault方法
...
static volatile EventBus defaultInstance;
...
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
但是EventBus並非嚴格的DCL單例模式,因為構造器並不是私有的,因此並不能保證EventBus例項的唯一性。不同例項之間是匯流排隔離的,即EventBus例項A釋出的事件event並不能被EventBus例項B訂閱接受。
register方法向EventBus註冊訂閱者
register(this)就是在當前類(Activity等)遍歷所有方法,按照EventBus事件處理方法的命名規則約束(onEvent開頭,只有一個引數,非static,非abstract的public方法)過濾出事件處理方法然後進行儲存。
因此register主要分為兩步:
- 找到訂閱者訂閱的所有事件
- 對每個訂閱者訂閱的所有事件,逐一進行訂閱
private synchronized void register(Object subscriber, boolean sticky, int priority) {
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
`
}
EventBus針對每一個事件,維護了一個訂閱了這個事件的訂閱者的列表,並且用HashMap來儲存,HashMap的鍵為事件的Class,值為該事件的訂閱者列表。
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
該HashMap有兩點需要注意:
- 該HashMap是物件屬性不是類屬性,因此不同的EventBus例項的該屬性是不同的,這也是不同EventBus例項之間匯流排隔離的原因
- 訂閱者列表並沒有通過ArrayList實現,為了達到執行緒併發安全,採用CopyOnWriteArrayList
post方法向EventBus釋出事件
EventBus儲存了事件到及該事件的訂閱者列表,因此釋出事件就應該是找到該事件的訂閱者列表,對每一個訂閱者通過反射呼叫事件處理方法。
/** 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;
}
}
}
post方法首先獲取了事件釋出者的執行緒狀態,從該執行緒狀態中獲取事件佇列,然後不停得處理佇列中的事件,直到佇列為空
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
postSingleEvent方法裡邊進一步呼叫了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;
}
首先在同步程式碼塊中獲取該事件的訂閱者列表
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
然後遍歷訂閱者列表,對每一個訂閱者進行處理
for (Subscription subscription : subscriptions) {
...
postToSubscription(subscription, event, postingState.isMainThread);
...
}
而postToSubscription方法就是根據不同的執行緒模式,在不同的執行緒中反射呼叫訂閱者的事件處理方法。
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);
}
}
unregister方法-訂閱者向EventBus解註冊
為了防止記憶體洩露,像Android中的廣播或者其他的訂閱者模式,有註冊就有解註冊,EventBus也不例外。unregister方法一般在與呼叫register方法對稱的生存週期回撥中呼叫。
/** 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) {
unubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
在register註冊的時候,儲存了兩個重要的HashMap,其中一個就是上文提到的事件到訂閱者列表的map,該map用於釋出事件的時候尋找該事件的訂閱者列表,然後通過反射呼叫訂閱者的事件處理方法。第二個就是訂閱者到事件列表的map,表明了該訂閱者訂閱了哪些事件,主要用於解註冊。
private final Map<Object, List<Class<?>>> typesBySubscriber;
unregister方法先從此map中找到該訂閱者訂閱的所有事件
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
然後對每一個事件進行解註冊
for (Class<?> eventType : subscribedTypes) {
unubscribeByEventType(subscriber, eventType);
}
最後從此map中刪除該訂閱者
typesBySubscriber.remove(subscriber);
本文結束,接下來會深入原始碼,從細節上探索EventBus的設計思路。
相關文章
- EventBus原始碼分析(四):執行緒模型分析(2.4版本)原始碼執行緒模型
- 原始碼分析一:EventBus原始碼
- EventBus原始碼分析(二):register方法儲存事件的訂閱者列表(2.4版本)原始碼事件
- Vue原始碼: 建構函式入口Vue原始碼函式
- EventBus原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- 5.go語言函式提綱Go函式
- EventBus 原始碼分析(上篇)原始碼
- EventBus 3.0 原始碼分析原始碼
- Yii2原始碼分析(一):入口原始碼
- Abp領域事件(EventBus)原始碼解析事件原始碼
- ThinkPHP3.1.3原始碼分析(一) 入口檔案分析PHP原始碼
- count 函式原始碼分析函式原始碼
- jQuery 原始碼分析第一篇之入口原始碼jQuery原始碼
- EventBus詳解及原始碼分析原始碼
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- slab原始碼分析--kmalloc函式分析原始碼函式
- main函式的入口函式AI函式
- Django原始碼分析之執行入口Django原始碼
- dll 入口函式函式
- vue原始碼分析系列之入口檔案分析Vue原始碼
- EventBus原始碼解析原始碼
- slab原始碼分析--銷燬函式原始碼函式
- 核心堆分配函式brk()原始碼分析函式原始碼
- EventBus原始碼分析(三):post方法釋出事件【獲取事件的所有訂閱者,反射呼叫訂閱者事件處理方法】(2.4版本)原始碼事件反射
- EventBus 3.1.1 原始碼解析原始碼
- EventBus原始碼學習原始碼
- redux原始碼分析之四:compose函式Redux原始碼函式
- OpenCV官方提綱OpenCV
- 程式碼規範技術交流提綱
- 入口檔案開始,分析Vue原始碼實現Vue原始碼
- Vue中之nextTick函式原始碼分析Vue函式原始碼
- slab原始碼分析--setup_cpu_cache函式原始碼函式
- Google guava原始碼之EventBusGoGuava原始碼
- EventBus3.0原始碼解析S3原始碼
- EventBus原理與原始碼解析原始碼
- Android EventBus原始碼解析Android原始碼
- Tomcat詳解系列(3) - 原始碼分析準備和分析入口Tomcat原始碼