EventBus
是一個用於元件間通訊的框架。它為開發提供一種非常簡便的方式來是實現元件間解耦通訊,並且提供了執行緒切換、優先順序設定等功能。
從官方的示意圖中不難看出,EventBus
使用的是觀察者模式
:Subscriber
註冊到EventBus
, 當Publisher
使用post
方法將Event
傳送給EventBus
,EventBus
就會回撥Subscriber
的onEvent
方法。觀察者模式能將觀察者和訂閱者最大程度的解耦,這也是EventBus
的功能所在。
具體用法就不多說了,具體可見官方主頁github.com/greenrobot/…
本文解析使用的EventBus
版本是3.0.0
。
進行原始碼分析之前,先思考一下,如果是自己,會如何實現?
首先,我們需要將註冊的類、宣告的訂閱方法,以及方法執行的執行緒、優先順序都儲存下來; 其次,我們要可以根據接收的事件去快速查詢到對應的訂閱者,當有訊息通知時,可以高效通知; 再者,我們還需要自己去處理執行緒間的切換以滿足不同的應用場景; 最後,我們應該提供登出功能以便取消訊息訂閱。
帶著思路,一起來看看原始碼是如何一步一步實現的。本文根據我們使用EventBus
的步驟來進行講解的。
註解@Subscribe
在宣告訂閱方法時,要求使用
@Subscribe
註解,先來看下它的具體定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
複製程式碼
先來看三個元註解:
@Documented
:生成java文件時,會將該註解也寫進文件裡
@Retention(RetentionPolicy.RUNTIME)
:有效週期是在執行時
@Target({ElementType.METHOD})
:指定對方法有效。
註解裡宣告瞭三個成員變數:
threadMode
(訂閱方法執行的執行緒)sticky
(是否是粘性事件)priority
(優先順序)。
ThreadMode
是一個列舉類,定義了四種執行緒模式:
public enum ThreadMode {
POSTING,
MAIN,
BACKGROUND,
ASYNC
}
複製程式碼
POSTING: 和傳送事件的執行緒在同一個執行緒,避免了執行緒切換開銷。
MAIN:訂閱在主執行緒,事件將排隊等待交付(非阻塞)。使用此模式的訂閱者必須快速返回,以避免阻塞主執行緒。
BACKGROUND:如果是在主執行緒釋出,則會訂閱在一個後臺執行緒,依次排隊執行;如果不是在主執行緒釋出,則會訂閱在釋出所在的執行緒。
ASYNC: 在非主執行緒和釋出執行緒中訂閱。當處理事件的方法 是耗時的,需要使用此模式。儘量避免同時觸發大量的耗時較長的非同步操作,EventBus 使用執行緒池高效的複用已經完成非同步操作的執行緒。
EventBus
之所以要求在訂閱方法上加上@Subscribe
註解,就是相當於給訂閱者打標籤,框架根據註解去找到訂閱者。
註冊
先來看看註冊的過程
public void register(Object subscriber) {
//拿到訂閱者的執行時型別Class
Class<?> subscriberClass = subscriber.getClass();
//利用訂閱者的Class去查詢類中宣告的訂閱方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
//迴圈遍歷逐個將訂閱者和訂閱方法訂閱到EventBus
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
複製程式碼
主要有三步,第一步拿到訂閱者的類物件,第二步通過訂閱者類物件找到類中的所有訂閱方法,第三步進行訂閱。
查詢訂閱方法
在上述第二步使用了一個SubscriberMethodFinder
例項來進行方法查詢。SubscriberMethodFinder
這個類是專門用來查詢訂閱方法的,findSubscriberMethods()
最後返回了一個SubscriberMethod
集合。SubscriberMethod
類則就是對我們宣告的訂閱方法和引數的封裝。可以先略過。
接著看findSubscriberMethods()
是如何通過訂閱者的Class物件來進行方法查詢的。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//從快取中查詢,如果已經有,則直接返回。
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//如果快取中沒有查詢到,就通過訂閱者Class物件進行查詢。
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 使用apt提前解析的訂閱者資訊
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
,因為查詢是比較耗時的操作,快取可以提高效率。
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
複製程式碼
METHOD_CACHE
是一個以訂閱者Class為Key
, 訂閱方法集合為Value的執行緒安全的的HashMap
。
第一次執行肯定是沒有快取的,然後會根據ignoreGeneratedIndex
來執行不同的方法。從方法名來看,一個是使用反射去查詢,另一個是使用已有的資訊去查詢。
其實這裡就是3.0.0引入的優化點:3.0.0引入了APT(Annotation Processing Tool)
,它可以在編譯階段就提前解析註解,提前檢索到訂閱方法。這樣就提高了執行時效率。
ignoreGeneratedIndex
這個值預設是false,因為反射開銷大,所以預設是走findUsingInfo()
分支,但是在findUsingInfo()
方法中會檢查本地是否有apt
預先解析出的訂閱者資訊,如果沒有,還是會執行反射方法findUsingReflectionInSingleClass()
。
啟動apt
的部分涉及到了註解處理器,所以會單獨起一篇來講解,先來看看不使用apt
的情況:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
//準備一個FindeState例項
FindState findState = prepareFindState();
//將訂閱者Class與FindeState例項關聯
findState.initForSubscriber(subscriberClass);
//從子類到父類去逐一查詢訂閱方法
while (findState.clazz != null) {
//使用反射查詢一個類的訂閱方法 findUsingReflectionInSingleClass(findState);
//將父類賦給findState.clazz,往上進行查詢
findState.moveToSuperclass();
}
//返回訂閱方法集合並回收FindState例項
return getMethodsAndRelease(findState);
}
複製程式碼
這裡去查詢訂閱方法,因為子類會繼承父類的方法,所以當子類找不到時,需要去查詢父類。這裡使用了一個while迴圈遞迴進行查詢。查詢過程使用了一個新的物件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;
}
複製程式碼
接著往下走,進入真正開始使用反射解析一個類的訂閱方法:findUsingReflectionInSingleClass()
:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 只獲取非繼承的方法,這個方法比getMethods()方法效率高,比如對一個Activity來說。 findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// 異常則直接獲取所有方法(包括繼承的), 這樣就不用再檢查父類了。
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
// 遍歷所有方法篩選出訂閱方法
for (Method method : methods) {
int modifiers = method.getModifiers();
// 必須是public的、非靜態、非抽象的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//獲取到所有引數的型別
Class<?>[] parameterTypes = method.getParameterTypes();
// 只能有一個引數
if (parameterTypes.length == 1) {
//獲取@Subscribe註解的方法
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
//檢查該eventType是否已訂閱了,通常訂閱者不能有多個 eventType 相同的訂閱方法
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//將訂閱方法和對應的接收的Event型別以及註解引數儲存
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");
}
}
}
複製程式碼
這個方法主要是通過修飾符、引數個數、指定註解和是否有EventType
相同的方法這幾層篩選,最終將訂閱方法新增進 findState
的 subscriberMethods
這個 List 中。
其中重點有一個判斷:findState.checkAdd()
,這個方法決定了是否訂閱方法可以被儲存下來進而能接收到訊息。一起來看看它是如何判斷的:
private boolean checkAdd(Method method, Class<?> eventType) {
// 1.檢查eventType是否已經註冊過對應的方法(一般都沒有)
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
return true;
} else {
// 2. 如果已經有方法註冊了這個eventType
if (existing instanceof Method) {
if (!checkAddWithMethodSignature((Method) existing, eventType)) {
throw new IllegalStateException();
}
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
複製程式碼
進行了兩種檢查,第一種是判斷當前類中是否已經有這個EventType
和對應的訂閱方法,一般一個類不會有對同一個EventType
寫多個方法,會直接返回true
,進行儲存。
但是如果出現了同一個類中同樣的EventType
寫了多個方法,該如何處理?
還有當findUsingReflection()
中進行下一輪迴圈,會進行父類查詢,如果子類繼承了父類的訂閱方法,又該如何處理呢?
答案就在上邊的註釋2。關鍵點就是checkAddWithMethodSignature()
方法:
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
// 以[方法名>eventType]為Key
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)) {
// methodClassOld == null或者 methodClassOld是methodClass的父類/同一個類
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
複製程式碼
對於同一類中同樣的EventType
寫了多個方法,因為方法名不同,所以[方法名>eventType]
的Key
不同,methodClassOld
會為null
,直接返回 true
。所以這種情況會將所有相同EventType
的方法都進行儲存。
對於子類重寫父類方法的情況,則methodClassOld
(即子類)不為null
,並且methodClassOld
也不是methodClass
的父類,所以會返回false
。即對於子類重寫父類訂閱方法,只會儲存子類的訂閱方法,忽略父類的訂閱方法。
至此,findState
的查詢任務就結束了,通過迴圈向父類查詢,將訂閱者的訂閱方法都儲存在了其內部變數subscriberMethods
列表中。
最後,跳出迴圈,回到findUsingReflection()
方法中,最後返回了 getMethodsAndRelease(findState)
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
複製程式碼
很好理解,就是將findState
中的subscriberMethods
取出並返回。可以看到作者還是很細心的,將使用完的findState
例項置空恢復後又放回例項池中,將例項回收利用,節省了新的開銷。
再回到findSubscriberMethods()
方法中,將查詢的方法最後都存進了記憶體快取METHOD_CACHE中, 對應關係是訂閱類和它的訂閱方法:
METHOD_CACHE.put(subscriberClass, subscriberMethods);
複製程式碼
到這裡,查詢訂閱方法就結束了。
訂閱(subscribe())
回到register()
方法中的第三步,對上一步查詢到的訂閱方法集合進行了遍歷呼叫subscribe()
方法。來看看subscribe()
方法做了什麼事:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
// 例項一個Subscription物件,內部持有了訂閱者和訂閱方法
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// subscriptionsByEventType是以EventType為Key,以它對應Subscription集合的Map
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);
}
}
// 根據優先順序排序Subscription
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;
}
}
// typesBySubscriber儲存了訂閱者對應的所有EventType
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// 粘性訂閱方法要立即處理
if (subscriberMethod.sticky) {
// 預設為true
if (eventInheritance) {
// 看當前EventType是否是已有的stickyEvent的子類或父類
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);
}
}
}
複製程式碼
這裡其實只做了三件事:
- 將訂閱者和訂閱方法封裝到
subscriptionsByEventType
,它可以根據EventType
拿到所有的Subscription
物件,Subscription
物件中就有訂閱者和訂閱方法。這樣當有EventType
訊息過來時,可以快速的傳遞給訂閱者的訂閱方法。 - 將訂閱者和訂閱方法封裝到
typesBySubscriber
,它可以根據訂閱類拿到所有的EventType
。這樣當我們呼叫呼叫unregister(this)
時,就可以拿到EventType
,又根據EventType
拿到所有訂閱者和方法,進行解綁了。 - 如果當前訂閱方法是粘性方法,則立即去查詢是否有本地事件,有的話就立即投遞。
至此,我們的註冊就完成了。可以看到某些方法棧的呼叫還是非常深的,但是整體流程卻很簡單。這也是值得我們學習的地方。
登出
註冊對應的就是登出,當我們的訂閱者不再需要接收事件時,可以呼叫unregister
進行登出:
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
:
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 根據事件型別拿到所有的Subscription
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
// 遍歷所有Subscription,符合解除條件的進行remove
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--;
}
}
}
}
複製程式碼
相比註冊流程,登出流程就非常簡單了,就是把對應的註冊者和對應的註冊資訊從記錄中移除即可。
傳送事件
註冊完畢後,我們的訂閱者和訂閱方法都被記錄在了EventBus
裡,這時就可以給訂閱者們傳送事件了。EventBus
提供了兩種傳送方法post()
和postSticky()
。post()
傳送的是非粘性的事件,postSticky()
傳送的是粘性事件。
post
先來看看post()
傳送:
public void post(Object event) {
// 從當前執行緒中取出PostingThreadState
PostingThreadState postingState = currentPostingThreadState.get();
// 拿到EventType佇列
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()) {
// 投遞出去就remove掉
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
// 所有訊息都投遞完成
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
複製程式碼
第一步使用ThreadLocal
,是因為ThreadLocal
保證了資料只對當前執行緒可見,其他執行緒是不可見的,這樣的話當我們從不同的執行緒中去取資料,資料相當於是分開儲存,設定和讀取就會比較快。從當前執行緒中取出的是一個PostingThreadState
:
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
複製程式碼
PostingThreadState
主要包含了當前執行緒的Event
佇列、訂閱者資訊、事件等資料。接下來就是迴圈呼叫postSingleEvent()
方法進行投送了:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// 如果是可繼承的事件
if (eventInheritance) {
// 查詢Event的所有父類、介面類以及父類的介面類(Event的父類和介面也是Event)
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 根據查詢到的所有Class(Event),逐個尋找訂閱者,進行分發event
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));
}
}
}
複製程式碼
這個方法的作用很簡單,就是做了個分支處理:如果是可繼承的事件,則查詢到它的所有父事件,然後再往下分發。繼續看postSingleEventForEventType()
方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
// 可能同時在多個執行緒同時傳送Event,subscriptionsByEventType是共有常量,所以需要加鎖
synchronized (this) {
// 根據Event的型別拿到所有訂閱者
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
// 事件還是Event,但是可能會分發到訂閱它父類的訂閱者中
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;
}
複製程式碼
這個方法和上一個方法的職責也很單一,就是查詢所有訂閱者,然後遍歷進行通知。查詢用的是一個Map: postToSubscription
,就是在訂閱時生成的一個[EventType -> List<Subscription>]
Map, 這樣我們就可以根據EventType
查詢到所有訂閱者。接著看postToSubscription()
:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 根據訂閱執行緒模式進行switch
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
// 直接呼叫在本執行緒
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
// 如果就在主執行緒,則直接呼叫
invokeSubscriber(subscription, event);
} else {
// 如果不在主執行緒,則使用mainThreadPoster
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
// 如果在主執行緒,使用backgroundPoster
backgroundPoster.enqueue(subscription, event);
} else {
// 如果不在主執行緒,則直接在當前執行緒呼叫
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
// 啟動新的執行緒呼叫,asyncPoster
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
複製程式碼
這裡就是EventBus
的執行緒切換了,主要有POSTING
、MAIN
、BACKGROUND
、ASYNC
四種模式,四種模式在開篇已經介紹過了。這裡涉及到了invokeSubscriber()
方法和mainThreadPoster
、backgroundPoster
、asyncPoster
三個poster,接下來我們就分別來看下:
invokeSubscriber()
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
複製程式碼
invokeSubscriber()
就是直接在當前執行緒呼叫了訂閱者的method
物件,這裡呼叫了反射類Method
的方法invoke()
直接呼叫執行。
mainThreadPoster
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
複製程式碼
mainThreadPoster
是個自定義的類HandlerPoster
,它的目的是在主執行緒中呼叫訂閱方法,而EventBus
使用的就是我們熟悉的Handler
:
final class HandlerPoster extends Handler {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
// 用來最後呼叫方法
this.eventBus = eventBus;
// 最大處理時間
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
// 一個待處理訊息的佇列
queue = new PendingPostQueue();
}
...
...
}
複製程式碼
HandlerPoster
是自定義的Handler
,傳送訊息使用的是Looper.getMainLooper()
即主執行緒的Handler
。 內部定義了一個最大處理訊息時間,預設是10
毫秒,所以說我們一定不要在訂閱方法中做耗時操作。還維護了一個PendingPostQueue
,它是自定義的一個連結串列佇列,這裡猜測HandlerPoster
可能是自己維護了訊息佇列,來看下入隊方法:
void enqueue(Subscription subscription, Object event) {
// 獲取一個PendingPost例項
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// 入隊
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
// 主執行緒的handler傳送訊息,傳送到主執行緒
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
複製程式碼
這裡出現了一個新的類PendingPost
,它封裝了訂閱者subscription
例項和事件event
例項;它內部又維護了一個大小為10000
的PendingPost
(陣列集合)池,用來重複利用PendingPost
例項。然後將PendingPost
例項入隊到上一步說的PendingPostQueue
佇列中。接著使用主執行緒的Handler
傳送一個訊息。接下來就是在handleMessage()
中如何處理訊息了:
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
// 從佇列中取出一個pendingPost
PendingPost pendingPost = queue.poll();
// 如果佇列裡的訊息處理完畢,就直接跳出迴圈。
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
// 呼叫訂閱方法並會回收pendingPost
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
// 如果方法的執行時間超過最大執行時間(預設10毫秒)
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
複製程式碼
這裡就是使用了while迴圈,不斷從佇列中去取PendingPost
處理,但是加了個最大執行時間處理,因為是在主執行緒呼叫,所以一旦超時,就退出佇列,並重新嘗試去再進入佇列。
BackgroundPoster
再來看看BackgroundPoster
BackgroundPoster
的作用是將UI執行緒的訂閱方法排程在非UI執行緒中。即它是要執行在新的Thread
中的,而開啟執行緒我們最常用的就是Runnable
, 來看看原始碼:
final class BackgroundPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
...
...
}
複製程式碼
果不其然,BackgroundPoster
實現了Runnable
介面,這樣就可以被執行緒執行。其內部也是維護了EventBus
和一個PendingPost
佇列。
public void enqueue(Subscription subscription, Object event) {
// 從訊息池中構建一個PendingPost
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// 入隊
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
// 執行緒池排程執行
eventBus.getExecutorService().execute(this);
}
}
}
複製程式碼
和HandlerPoster
類似:新建一個新的PendingPost
入隊,使用了EventBus
裡的一個ExecutorService
,它是對執行緒池定義的一個介面,來看看它的預設值:
public class EventBusBuilder {
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
...
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
}
複製程式碼
熟悉的Executors
,建立了一個可快取的執行緒池,用來執行BackgroundPoster
這個Runnable
物件,再來看看BackgroundPoster
的run()
方法:
@Override
public void run() {
try {
try {
while (true) {
// 取出隊頭
PendingPost pendingPost = queue.poll(1000);
// 如果隊頭為空,說明佇列裡沒有訊息了,直接退出迴圈
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
// 呼叫訂閱方法
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
複製程式碼
也和HandlerPoster
類似,但是不同之處在於:
- 使用了
poll(int maxMillisToWait)
方法,這個設計很巧妙,當取到最後發現佇列為空後,會wait
1000 毫秒,當有有新的資訊來臨時就會喚醒執行緒,poll
出訊息。這樣設計就減少了傳送訊息的次數,節省了資源。 - 因為是在子執行緒執行,所以就沒有方法執行時間的限制了。
AsyncPoster
class AsyncPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
//直接開啟新的執行緒執行
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
// 直接取出訊息執行
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
}
複製程式碼
可以看到AsyncPoster
和BackgroundPoster``非常的相似,因為它們的功能也非常相似。但是不同之處在於:BackgroundPoster
是儘可能使用一個後臺線層去依次排隊執行訂閱方法;而AsyncPoster
則是每條訊息都直接開啟新的後臺執行緒立即執行。
至此四個Poster就講完了,看完是真的爽,不論是從功能抽象到具體細節的把控,EventBus
都處理的很好,非常值得學習。
粘性事件
粘性事件這名字一聽很耳熟,沒錯,安卓四大元件之BroadcastReceiver
就有一種廣播叫做粘性廣播(StickyBroadcast
),而EventBus
也提供了類似的功能:當註冊了粘性事件後,立即能收到還沒有註冊時系統發出的最後一個事件。
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);
}
複製程式碼
postSticky()
方法用來傳送一個粘性事件,在這個方法中,直接將粘性事件儲存在了一個Map集合中,而key
就是Event
的Class物件。接著就呼叫正常的post()
方法了。
那為什麼我們後註冊的方法也能接收到之前發出的粘性事件呢,答案就在上面提到的註冊方法subscribe()
中的最後一段:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
...
// 如果是粘性事件,則直接傳送出去
if (subscriberMethod.sticky) {
if (eventInheritance) {
// 從stickyEvents取出粘性事件的Class物件
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
// 如果訂閱的事件是儲存的粘性事件Class或它的父類
if (eventType.isAssignableFrom(candidateEventType)) {
// 取出快取的Event
Object stickyEvent = entry.getValue();
// 將快取的Event傳送出去
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
複製程式碼
在我們註冊訂閱方法和事件時,如果是粘性事件,就直接會將事件傳送給註冊了相同Event
的訂閱者,方法中呼叫了checkPostStickyEventToSubscription(newSubscription, stickyEvent)
方法:
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
複製程式碼
很簡單,直接又呼叫了postToSubscription()
方法,根據指定執行緒分別進行分發。
總結
整體看下來,框架的結構非常的清晰全面,尤其在對方法功能的封裝和細節處理上,很是值得學習。尤其在3.0後,EventBus
使用了註解處理器在編譯階段就完成了訂閱者的解析,這使得框架更佳的輕量和高效。整片原始碼讀下來還是很爽的,沒事還要再複習多看幾遍,很多東西都值得學習。共勉!