EventBus的使用和原始碼解析
一、基本介紹
EventBus 是一個 Android 事件釋出/訂閱框架,通過解耦釋出者和訂閱者簡化 Android 事件傳遞,這裡的事件可以理解為訊息,本文中統一稱為事件。事件傳遞既可用於 Android 四大元件間通訊,也可以使用者非同步執行緒和主執行緒間通訊等等。EventBus
EventBus3.0版本有較大的更新,效能上有很大提升。這裡主要介紹新版本。
傳統的事件傳遞方式包括:Handler、BroadCastReceiver、Interface 回撥,相比之下 EventBus 的優點是程式碼簡潔,使用簡單,並將事件釋出和訂閱充分解耦。類似框架OTTO。
二、EventBus & Otto對比
共同點
1、都是事件匯流排框架,滿足訊息/事件傳遞的同時,也實現了元件間的解耦.
2、註冊的共同點都是採用method方法進行一個整合。
3、都採用註解的方式來標註訂閱方法(舊版本的EventBus通過固定方法名標記訂閱者)
4、大部分規則相同,比如訂閱方法只能有一個引數。
5、都不適用程式間通訊
不同點
1、OTTO更加輕量級,結構簡單。EventBus稍微複雜一些。
2、OTTO預設在主執行緒中使用,不能在其他執行緒使用,通過設定ThreadEnforcer可以在任意執行緒使用,但是訊息傳遞不能指定目標執行緒,EventBus實現了4種ThreadMode,執行緒之間訊息傳遞非常靈活。
3、EventBus支援粘性事件,而OTTO不支援。即先發訊息,再註冊訂閱者仍然能夠收到訊息。
3、OTTO有預設的生產者方法,可以產生預設訊息,EventBus沒有
三、EventBus的簡單使用介紹
定義訊息
public class MessageEvent {
//定義相關屬性
}
註冊
eventBus.register(this);
定義訂閱者
@Subscribe
public void onEvent(MessageEvent event) {
//收到訊息後的處理
};
傳送訊息
eventBus.post(event);
解綁
eventBus.unregister(this);
四、原始碼解析
1、EventBus構造
通常我們呼叫EventBus.getDefault()獲取EventBus
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
這個方法是執行緒安全的,EventBus的無參構造方法使用預設構建器DEFAULT_BUILDER構造,EventBusBuilder指定了EventBus的一些行為,用於輸出log,查錯,除錯等。
logSubscriberExceptions 指定了收到SubscriberExceptionEvent型別訊息時丟擲異常的行為,預設為true,列印log,如果為false不輸出log
輸出的資訊有
Log.e(TAG, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass()
+ " threw an exception", cause);
SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event;
Log.e(TAG, "Initial event " + exEvent.causingEvent + " caused exception in "
+ exEvent.causingSubscriber, exEvent.throwable);
Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
+ subscription.subscriber.getClass(), cause);
對logNoSubscriberMessages指定了傳送了沒有訂閱者的訊息的行為,預設為true,列印log,如果為false不輸出log
輸出的資訊為
Log.d(TAG, "No subscribers registered for event " + eventClass);
sendSubscriberExceptionEvent指定是否傳送SubscriberExceptionEvent,預設為true
操作行為
SubscriberExceptionEvent exEvent =
new SubscriberExceptionEvent(this, cause, event,subscription.subscriber);
post(exEvent);
sendNoSubscriberEvent指定了沒有訂閱者時的行為,預設為true,傳送NoSubscriberEvent這樣一個訊息
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
throwSubscriberException指定了訂閱方法執行異常時是否丟擲異常,預設為false
if (logSubscriberExceptions) {
Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
+ subscription.subscriber.getClass(), cause);
}
eventInheritance指定是否發訊息給這個事件的父事件對應的訂閱者,預設為true。如果為false,只會發訊息給型別完全一致的訂閱者
if (eventInheritance) {
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);
}
ignoreGeneratedIndex指定是否忽略設定索引,這個值預設是false,這段程式碼中使用的兩種解析方式不容易看懂
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
findUsingReflection利用反射來獲取訂閱類中的訂閱方法資訊
findUsingInfo從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法資訊
findUsingInfo從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法資訊
findUsingInfo執行效率更高一些,預設使用這種。
strictMethodVerification指定是否嚴格校驗,預設為false。嚴格校驗會直接丟擲異常
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");
}
2、註冊與解析
註冊之後,解析出了所有的訂閱方法,方法引數型別
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
相關對映主要是兩個
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
subscriptionsByEventType的key是訂閱者訊息型別,value是Subscription,是具體訂閱者對應註冊物件和方法
typesBySubscriber的key是註冊物件,value是對應所有訂閱者訊息型別的list
解析器是SubscriberMethodFinder這個類,EventBus中有subscriberMethodFinder這個例項,呼叫
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)這個方法解析方法資訊
如果沒有訂閱者方法,會丟擲異常
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
如果已經註冊過,會丟擲異常
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
如果訂閱者是粘性事件,如果粘性事件不為空會直接傳送粘性事件。
粘性事件儲存結構為
private final Map<Class<?>, Object> stickyEvents;
stickyEvents的key是事件型別,Object是對應的具體事件。key,value唯一,說明粘性事件是會被覆蓋的。這樣可以節省記憶體開銷。
3、傳送訊息
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;
}
}
}
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);
}
post執行緒狀態通過ThreadLocal維護,保證執行緒隔離,不需要加鎖,提高執行效率。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
經過以下四個方法,事件分發到對應執行緒的對應訂閱者
private void postSingleEvent(Object event, PostingThreadState postingState) private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass)
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread)
void invokeSubscriber(Subscription subscription, Object event)
特別注意執行緒處理
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(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);
}
POSTING:是在當前執行緒執行
MAIN:拋到主執行緒
BACKGROUND:如果是在主執行緒,拋到新執行緒,如果不是在主執行緒,就在當前執行緒執行
ASYNC:拋到新執行緒中
沒有訂閱者的訊息預設會被重新包裝為NoSubscriberEvent,可以做一些全域性捕獲處理
4、解綁
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());
}
}
做一些清理工作,注意不要重複解綁。
剛開始已經說過了,EventBus跟OTTO很像,而 Otto 是基於 Guava 的增強的事件匯流排,Guava 是google一個很強大的java開源庫。EventBus改進是最多的,據說是效率最高的,這個沒有做過相關測試。但是從實現上來看,對效能優化處理還是做了很多事情。
歡迎掃描二維碼,關注公眾賬號
相關文章
- EventBus原始碼解析原始碼
- EventBus 3.1.1 原始碼解析原始碼
- EventBus3.0原始碼解析S3原始碼
- EventBus原理與原始碼解析原始碼
- Android EventBus原始碼解析Android原始碼
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- Android EventBus原始碼解析 帶你深入理解EventBusAndroid原始碼
- Abp領域事件(EventBus)原始碼解析事件原始碼
- Android開源庫——EventBus原始碼解析Android原始碼
- Picasso的使用和原始碼解析原始碼
- EventBus原始碼分析原始碼
- RxBinding使用和原始碼解析原始碼
- Binder的使用方法和原始碼解析原始碼
- EventBus原始碼學習原始碼
- EventBus 原始碼分析(上篇)原始碼
- EventBus 3.0 原始碼分析原始碼
- 原始碼分析一:EventBus原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- net/http包的使用模式和原始碼解析HTTP模式原始碼
- Google guava原始碼之EventBusGoGuava原始碼
- 聊一聊 EventBus 原始碼和設計之禪原始碼
- spark的基本運算元使用和原始碼解析Spark原始碼
- Retrofit2使用方式和原始碼解析原始碼
- EventBus詳解及原始碼分析原始碼
- redis原始碼解析----epoll的使用Redis原始碼
- Spring @Profile註解使用和原始碼解析Spring原始碼
- TextWatcher的使用及原始碼解析原始碼
- GYHttpMock:使用及原始碼解析HTTPMock原始碼
- EventBus 原理解析
- Android依賴注入Dagger的使用和原始碼解析(上篇)Android依賴注入原始碼
- ffmpeg在iOS的使用-iFrameExtractor原始碼解析iOS原始碼
- 三方庫原始碼筆記(1)-EventBus 原始碼詳解原始碼筆記
- RecyclerView用法和原始碼深度解析View原始碼
- HandlerThread和IntentService原始碼解析threadIntent原始碼
- Eventbus 使用方法和原理分析
- Android開源框架原始碼鑑賞:EventBusAndroid框架原始碼
- Android中IntentService的使用及其原始碼解析AndroidIntent原始碼
- redux的原始碼解析Redux原始碼