EventBus是目前比較常用的一個消息總線,用于應(yīng)用程序內(nèi)部的通信茅诱。今天我們就來聊聊EventBus的相關(guān)內(nèi)容。
本文的要點(diǎn)如下:
- 概述
- 使用方法
- 粘性事件
- 源碼分析
- 注冊過程
- 事件的發(fā)布
- 總結(jié)
概述
EventBus是一個Android端優(yōu)化的publish/subscribe消息總線肩钠,簡化了應(yīng)用程序內(nèi)各組件間挡鞍、組件與后臺線程間的通信。
在沒使用EventBus之前售躁,我們開發(fā)中也會發(fā)布訂閱模式的機(jī)制來處理一些問題坞淮,如果需要用到的地方較多,則每一處需要用到事件的發(fā)布訂閱的地方陪捷,都需要實(shí)現(xiàn)一整套發(fā)布訂閱的機(jī)制回窘,比如定義Listener接口,定義Notification Center/Observable市袖,定義事件類啡直,定義注冊監(jiān)聽者的方法、移除監(jiān)聽者的方法苍碟、發(fā)布事件的方法等酒觅。我們不得不寫許多重復(fù)的冗余的代碼來實(shí)現(xiàn)我們的設(shè)計目的。
既然有重復(fù)冗余微峰,那么就一定可以將整套機(jī)制實(shí)現(xiàn)成一個框架舷丹,來實(shí)現(xiàn)高復(fù)用,減少代碼量蜓肆,提高效率颜凯,EventBus便是如此。
作為一個消息總線仗扬,EventBus主要有三個組成部分:
- 事件(Event):可以是任意類型的對象症概。通過事件的發(fā)布者將事件進(jìn)行傳遞。
- 事件訂閱者(Subscriber):接收特定的事件厉颤。
- 事件發(fā)布者(Publisher):用于通知 Subscriber 有事件發(fā)生穴豫。可以在任意線程任意位置發(fā)送事件逼友。
使用方法
1.添加依賴:
//eventbus
implementation 'org.greenrobot:eventbus:3.1.1'
2.定義事件類
public class MyEvent {
public String name;
public MyEvent(String name) {
this.name = name;
}
}
要發(fā)送事件精肃,我們就要有對應(yīng)的事件類,沒有具體的要求帜乞,可以根據(jù)需求進(jìn)行自定義司抱。
3.注冊/注銷事件
//注冊事件
@OnClick(R.id.registevent)
public void setRegistevent(){
EventBus.getDefault().register(this);
}
//處理事件
@Subscribe(threadMode = ThreadMode.MAIN, priority = 100)
public void getMyEvent(MyEvent event){
receive_event.setText(event.name);
}
//注銷事件
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
訂閱部分一般分為兩步,首先通過register(this)來表示該訂閱者進(jìn)行了訂閱黎烈,然后通過Subscribe注解习柠,getMyEvent(MyEvent event)方法表示指定對事件MyEvent的訂閱匀谣。
注意:在注冊之后一定要用unregister(this)注銷事件,以免內(nèi)存泄露资溃。
注解Subscribe中的參數(shù)threadMode 表示線程模式武翎,有四種選項(xiàng):
- ThreadMode.POSTING:事件的處理在和事件的發(fā)送在相同的進(jìn)程,所以事件處理時間不應(yīng)太長溶锭,不然影響事件的發(fā)送線程宝恶。
- ThreadMode.MAIN:事件的處理會在UI線程中執(zhí)行。事件處理時間不能太長趴捅,這個不用說了垫毙,長了會ANR的。
- ThreadMode.BACKGROUND:如果事件是在UI線程中發(fā)布出來的拱绑,那么事件處理就會在子線程中運(yùn)行综芥,如果事件本來就是子線程中發(fā)布出來的,那么事件處理直接在該子線程中執(zhí)行猎拨。所有待處理事件會被加到一個隊(duì)列中膀藐,由對應(yīng)線程依次處理這些事件,如果某個事件處理時間太長红省,會阻塞后面的事件的派發(fā)或處理消请。
- ThreadMode.Async:事件處理會在單獨(dú)的線程中執(zhí)行,主要用于在后臺線程中執(zhí)行耗時操作类腮,每個事件會開啟一個線程臊泰。
注解Subscribe中的參數(shù)priority 表示事件優(yōu)先級,事件的優(yōu)先級類似廣播的優(yōu)先級蚜枢,優(yōu)先級越高優(yōu)先獲得消息缸逃。
另外,EventBus允許對事件進(jìn)行截斷:
EventBus.getDefault().cancelEventDelivery(event);
4.發(fā)送事件
@OnClick(R.id.bt_sendevent)
public void sendEvent(){
EventBus.getDefault().post(new MyEvent("0123"));
}
發(fā)送事件就比較簡單了厂抽,我們可以在代碼的任何位置發(fā)布事件需频,當(dāng)前所有事件類型與發(fā)布的事件類型匹配的訂閱者都將收到它。
粘性事件
EventBus除了普通事件也支持粘性事件筷凤。
粘性事件:訂閱在發(fā)布事件之后昭殉,但同樣可以收到事件(類似于粘性廣播)。訂閱/解除訂閱和普通事件一樣藐守,但是處理訂閱的方法有所不同挪丢,需要注解中添加sticky = true。 用法如下:
@Subscribe(priority = 100,sticky = true)
public void getMyEvent(MyEvent event){
receive_event.setText(event.name);
}
發(fā)送事件:
EventBus.getDefault().postSticky(new MyEvent("這是一條粘性事件"));
移除粘性事件:
//移除某種特定類型的粘性事件
EventBus.getDefault().removeStickyEvent(MyEvent.class);
//移除所有粘性事件
EventBus.getDefault().removeAllStickyEvents();
源碼分析
從用法中不難看出卢厂,獲取EventBus實(shí)例用到的是getDefault方法乾蓬,那么我們來看看源碼:
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
很熟悉,有木有慎恒?單例模式啊任内,標(biāo)準(zhǔn)的雙重檢驗(yàn)鎖的寫法撵渡。不過有個問題,EventBus的構(gòu)造函數(shù)是public而不是private死嗦,似乎違背了單例模式的原則趋距,但其實(shí)是由EventBus的功能決定的,EventBus 是可以創(chuàng)造出很多條總線的越除,在不同的總線之間是不能傳遞事件的(不能通信)棚品。這樣的設(shè)計是為后面可以創(chuàng)建不同的總線。
接著來看EventBus的構(gòu)造函數(shù):
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
EventBus類提供了一個public的構(gòu)造函數(shù)廊敌,可以用默認(rèn)的EventBusBuilder配置來構(gòu)造EventBus對象。
注冊過程:
還是回過頭來看register方法:
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實(shí)例的findSubscriberMethods方法來獲取該觀察者類型中的所有訂閱方法骡澈,最后通過subscribe方法將所有的訂閱方法分別進(jìn)行訂閱。
下面我們先看下查找訂閱者的方法:
SubscriberMethod類封裝了訂閱方法(使用@Subscribe注解的方法)類型的信息掷空。
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
//省略代碼
}
從代碼中我們不難看出肋殴,實(shí)際上該類就是通過幾個字段來存儲@Subscribe注解中指定的類型信息,以及一個方法的類型變量坦弟。
SubscriberMethodFinder類中的findSubscriberMethods方法:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//首先從緩存當(dāng)中嘗試找該訂閱者的訂閱方法
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
//找到直接返回
return subscriberMethods;
}
//當(dāng)緩存中沒有找到該觀察者的訂閱方法的時候使用下面的方法獲取方法信息
if (ignoreGeneratedIndex) {
//ignoreGeneratedIndex參數(shù)表示是否忽略注解器生成的MyEventBusIndex护锤,默認(rèn)為false
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;
}
}
可以看到,邏輯并不難理解赤炒,就是先找緩存氯析,如果緩存中沒有再獲取,獲取后加緩存然后返回莺褒。
那么我們來具體看看獲取方法信息的關(guān)鍵方法:findUsingInfo()
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
//過FindState對象用來存儲找到的方法信息
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//從當(dāng)前類開始遍歷該類的所有父類
while (findState.clazz != null) {
//獲取訂閱者信息
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
//如果使用了MyEventBusIndex掩缓,將會進(jìn)入到這里并獲取訂閱方法信息
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//未使用MyEventBusIndex將會進(jìn)入這里使用反射獲取方法信息
findUsingReflectionInSingleClass(findState);
}
//將findState.clazz設(shè)置為當(dāng)前的findState.clazz的父類,有點(diǎn)類似于鏈表的遍歷
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
其中關(guān)鍵的方法是findUsingReflectionInSingleClass()遵岩,對當(dāng)前類中聲明的所有方法進(jìn)行校驗(yàn)你辣,并將符合要求的方法的信息封裝成一個SubscriberMethod對象添加到了FindState對象中的列表中。
獲取到訂閱方法后尘执,我們回到subscribe()方法:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//將所有的觀察者和訂閱方法封裝成一個Subscription對象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//嘗試從緩存中根據(jù)事件類型來獲取所有的Subscription對象
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//指定的事件類型沒有對應(yīng)的觀察對象的時候
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//根據(jù)新加入的方法的優(yōu)先級決定插入到隊(duì)列中的位置
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;
}
}
//從“訂閱者-事件類型”列表中嘗試獲取該訂閱者對應(yīng)的所有事件類型
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//黏性事件
if (subscriberMethod.sticky) {
if (eventInheritance) {
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);
}
}
}
代碼比較長舍哄,我們來理一下邏輯:
首先,將傳進(jìn)來的觀察者和訂閱方法封裝成一個Subscription對象誊锭,這里才算是真正的訂閱蠢熄。
然后,從CopyOnWriteArrayList中根據(jù)事件類型來獲取所有的Subscription對象組成炉旷,根據(jù)新加入的方法的優(yōu)先級決定插入到隊(duì)列中的哪個位置签孔。
然后叉讥,獲取指定的觀察者對應(yīng)的全部的觀察事件類型,這里也是通過一個哈希表來維護(hù)這種映射關(guān)系的饥追。
最后图仓,根據(jù)當(dāng)前的訂閱方法是否是黏性的,來決定是否將當(dāng)前緩存中的信息發(fā)送給新訂閱的方法但绕。
至此救崔,注冊過程就基本結(jié)束了,注銷的邏輯比較比較簡單捏顺,基本上就是注冊操作反過來六孵,刪除緩存中的訂閱方法。
事件的發(fā)布:
發(fā)布事件的流程是從post方法開始的:
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
public void post(Object event) {
//獲取當(dāng)前線程的狀態(tài)信息
PostingThreadState postingState = currentPostingThreadState.get();
//獲取當(dāng)前事件隊(duì)列
List<Object> eventQueue = postingState.eventQueue;
//將當(dāng)前要發(fā)送的事件加入到隊(duì)列中
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
//隊(duì)列不空就一直循環(huán)發(fā)送事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
//恢復(fù)當(dāng)前線程的信息
postingState.isPosting = false;
postingState.isMainThread = false;
}
邏輯并不復(fù)雜幅骄,首先從currentPostingThreadState中取出當(dāng)前線程的狀態(tài)信息劫窒。currentPostingThreadState是ThreadLocal類型的對象,其中保存者PostingThreadState類型的對象拆座,里面儲存了當(dāng)前線程對應(yīng)的事件列表和線程的狀態(tài)信息等主巍。
然后,將當(dāng)前要發(fā)送的事件加入到隊(duì)列eventQueue中挪凑。
最后孕索,通過循環(huán)調(diào)用postSingleEvent()方法取出事件并進(jìn)行發(fā)布。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
//eventInheritance為ture會查找該事件的所有父類
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//處理事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//找不到事件會拋異常
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
可以看到最終使用的是postSingleEventForEventType()方法對每個事件類型進(jìn)行處理躏碳。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
//獲取指定的事件對應(yīng)的所有的觀察對象
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//遍歷觀察對象搞旭,并最終執(zhí)行事件的分發(fā)操作
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;
}
這里的邏輯就非常清晰了,通過傳入的事件類型到緩存中取尋找它對應(yīng)的全部的Subscription菇绵,然后對得到的Subscription列表進(jìn)行遍歷选脊,并依次調(diào)用postToSubscription方法執(zhí)行事件的發(fā)布操作。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
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);
}
}
終于脸甘,我們看到了在訂閱消息時設(shè)置的ThreadMode參數(shù)恳啥,switch語句中會根據(jù)當(dāng)前的線程狀態(tài)和訂閱方法指定的ThreadMode信息來決定合適觸發(fā)方法。
這里的invokeSubscriber會在當(dāng)前線程中立即調(diào)用反射來觸發(fā)指定的觀察者的訂閱方法丹诀。
如果當(dāng)前線程不符合ThreadMode參數(shù)中的信息钝的,會根據(jù)具體的情況將事件加入到不同的隊(duì)列中進(jìn)行處理。
mainThreadPoster最終繼承自Handler铆遭,當(dāng)調(diào)用它的enqueue方法的時候硝桩,它會發(fā)送一個事件并在它自身的handleMessage方法中從隊(duì)列中取值并進(jìn)行處理,從而達(dá)到在主線程中分發(fā)事件的目的枚荣。
backgroundPoster實(shí)現(xiàn)了Runnable接口碗脊,它會在調(diào)用enqueue方法的時候,拿到EventBus的ExecutorService實(shí)例橄妆,并使用它來執(zhí)行自己衙伶。在它的run方法中會從隊(duì)列中循環(huán)取值來進(jìn)行執(zhí)行祈坠。
至此,事件的發(fā)布過程也基本結(jié)束矢劲。
總結(jié)
EventBus是一個不錯的開源消息總線庫赦拘,使用了諸如單例模式、觀察者模式芬沉、builder模式等設(shè)計模式躺同。它簡化了應(yīng)用程序內(nèi)各組件間、組件與后臺線程間的通信丸逸,解耦了事件的發(fā)送者和接收者蹋艺,避免了復(fù)雜的、易于出錯的依賴及生命周期問題黄刚,甚至完全可以代替Intent捎谨,boardcast等Android傳統(tǒng)的方法在Fragment,Activity隘击,Service以及不同線程之間傳遞數(shù)據(jù)⊙忻可以使我們的代碼更加簡潔埋同、健壯。