一、簡介
EventBus是一個事件“發(fā)布/訂閱”總線,可用于Android或者Java項目中。它具有以下優(yōu)點(diǎn):
- 簡化組件間的通信
- 解耦事件發(fā)布方和訂閱方;
- 很好的實現(xiàn)在Activities、Fragments和后臺線程間通信;
- 避免復(fù)雜的易錯的依賴和生命周期問題;
- 使代碼更簡潔;
- SDK體積很?。s50k);
- 已經(jīng)在上億的安裝應(yīng)用中使用;
- 具有高級特性,如跨線程事件分發(fā)、按優(yōu)先級分發(fā)等。

下文我們把Publisher翻譯成
事件發(fā)布器,把Subscriber翻譯成事件訂閱者,Event翻譯成事件,EventBus翻譯成總線。
二、EventBus使用三部曲
1. 定義事件類型
public class MessageEvent {
public String msg;
}
2. 事件訂閱
public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
// 將事件訂閱者注冊到總線中
EventBus.getDefault().register(this);
}
@Override
protected void onPause() {
super.onPause();
// 將事件訂閱者從總線中注銷掉
EventBus.getDefault().unregister(this);
}
// 定義事件接收器處理事件的方法
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.d(TAG, "onMessageEvent="+event.msg);
}
}
3. 事件發(fā)布
public void sendEventMsg(View v){
// 事件發(fā)布器發(fā)布事件
MessageEvent event = new MessageEvent();
event.msg = "hello";
EventBus.getDefault().post(event);
}
三、源碼分析
正如官方文檔介紹,sdk真的很小。核心類EventBus.java就500行代碼完成了所有功能。短小精悍、簡潔易用、穩(wěn)定高效,實為我輩開發(fā)之楷模。EvenBus核心任務(wù)就兩個:1、事件訂閱者的管理;2、事件的分發(fā)。先總的看下整體架構(gòu),類圖大致如下:

1. EventBus的創(chuàng)建
EventBus SDK使用不需要任何初始化操作。在需要訂閱或者發(fā)布事件時,通過EventBus.getDefault()方法獲取一個全局的EventBus的單例來執(zhí)行相關(guān)操作。下面是SDK源碼中單例創(chuàng)建的代碼,沒什么好說的標(biāo)準(zhǔn)的單例創(chuàng)建。
// double check lock
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
2. EventBus對事件訂閱者的管理邏輯
上面EventBus使用部分介紹了,事件接收器的注冊/注銷是通過EventBus實例的register/unregister方法來完成的。我們重點(diǎn)分析下register方法的實現(xiàn),unregister方法類似就不重復(fù)分析了。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
事件訂閱者注冊register(Object subscriber)方法接收一個Object對象作為入?yún)ⅰ?/p>
- 通過
findSubscriberMethods(Class<?> subscriberClass)方法分析出該對象的類型對象中所有事件處理方法。是通過反射的方式找到我們用@Subscribe注解的方法,如下代碼所示。 - 將Object對象和上面找到的每個事件處理方法都分別組成一個事件訂閱者
Subscription,也就是說如果一個訂閱對象類里面實現(xiàn)了多個事件處理方法,那就會生成多個事件訂閱者。然后將這些事件訂閱者對象放到集合Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType中保存起來,待事件分發(fā)時使用。subscriptionsByEventType是一個map,map的key是事件類型,value是相同事件的訂閱者的列表。插入到列表的時候是按照訂閱者優(yōu)先級順序插入的,后續(xù)事件分發(fā)的時候就會按照事件優(yōu)先級來順序分發(fā)。
類比上面使用說明中的例子,我們傳入的Object對象是一個Activity對象,事件處理方法就是onMessageEvent方法。在EventBus中是將這個Activity對象和onMessageEvent方法和成了一個事件訂閱者Subscription,然后存在數(shù)劇集合中的。上面類圖中事件訂閱者Subscription的結(jié)構(gòu)清楚的展示了這一點(diǎn)。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods = findState.clazz.getDeclaredMethods();
for (Method method : methods) {
int modifiers = method.getModifiers();
// 1、必須是public類型,且不能是被static、abstract修飾的方法
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 2、方法有且只有一個參數(shù)
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
//3、方法必須是被Subscribe注解的方法
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()));
}
}
} 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");
}
}
}
unregister的過程我們就不分析了,類似register的逆過程,就是從數(shù)據(jù)集合中移除對應(yīng)的事件訂閱者的過程。
3. EventBus對事件的分發(fā)邏輯
事件發(fā)布post(Object event)方法接收一個事件對象作為入?yún)?。還記得我們事件訂閱者是存在以事件類型為key的map中,那事情就簡單了,通過入?yún)⒌氖录愋瞳@取所有訂閱此事件的事件訂閱者列表。
- 執(zhí)行事件訂閱者
Subscription中事件訂閱對象的事件處理方法;
void invokeSubscriber(Subscription subscription, Object event) {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
}
- 跨線程分發(fā)
EventBus中有兩個poster。mainThreadPoster:如類圖所示它繼承了Handler,關(guān)聯(lián)的是主線程的Looper,所以可以通過mainThreadPoster將事件發(fā)送到主線程執(zhí)行;backgroundPoster:如果所示本身是個Runnable,關(guān)聯(lián)了一個線程池,所以它是將事件發(fā)送到線程池去執(zhí)行的。
還有一種類型是不適用上面的poster,直接就在事件發(fā)布器調(diào)用post(Object event)方法的線程執(zhí)行事件處理方法,這就實現(xiàn)了在當(dāng)前線程分發(fā)事件。
四、學(xué)習(xí)
1、Double-Check單例創(chuàng)建法
- Double-Check是為了執(zhí)行提高效率,只在首次獲取實例的時候有線程同步的開銷,后續(xù)獲取實例時不涉及同步;
- 不同步的引用對象不是線程安全的,除double和float外的基本變量類型是線程安全的。所以在double check模式下引用對象、double和float變量都需要使用volatile 關(guān)鍵字來保證其線程安全。
2、注解
RetentionPolicy.RUNTIME類型的注解可以在運(yùn)行時通過反射的方式獲取到注解的內(nèi)容。如下運(yùn)行時注解Subscribe可應(yīng)用到方法上。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
運(yùn)行時通過反射獲取到注解的相關(guān)信息。
Method[] methods = findState.clazz.getDeclaredMethods();
for (Method method : methods) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
}
}
3、多線程編程
根據(jù)前文的介紹可以了解整個框架中的臨界資源應(yīng)該是這個Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType數(shù)據(jù)集合對象了。在注冊/注銷時間訂閱者和事件分發(fā)是都需要操作這個數(shù)據(jù)集合,可能出現(xiàn)多個線程同時注冊/注銷時間訂閱者,或者注冊/注銷時間訂閱者和事件分發(fā)不在同一個線程,這些場景都有多線程的問題。
我們來看看作者是怎么處理的。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
public synchronized void unregister(Object subscriber) {
... ...
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
... ...
return false;
}
對subscriptionsByEventType的操作使用synchronized關(guān)鍵字來同步。另外對于事件訂閱者列表使用CopyOnWriteArrayList來同步修改列表,同時讓列表的遍歷更加高效。