EventBus這是一個目前被廣泛使用的,用於在不同的介面(Activity/Fragment)之間傳遞資料的三方庫,其簡單的使用深受廣大開發者喜歡。
相比起Bundle或者介面回撥,EventBus使用起來更加簡單快捷,但有一點需要注意,再使用EventBus的時候,你需要對自己業務中的通知分發有很清晰的瞭解,不然很容易導致分發過多的無用通知,導致效能的消耗.
本文會對EventBus做一個簡單的介紹
簡單的使用
1.oncreate()中註冊
EventBus.getDefault().register(this);
複製程式碼
2.onDestroy()銷燬
EventBus.getDefault().unregister(this);
複製程式碼
3.使用@Subscribe
註解方法,用來接收事件通知
@Subscribe
public void onMainEvent(String str){
System.out.println("event = "+str);
}
複製程式碼
4.傳送通知
EventBus.getDefault().post("12456");
複製程式碼
上述4個步驟就能完成一次簡單的事件分發,這也是EventBus被廣泛使用的部分原因。
原始碼解讀
在深入原始碼之前先解釋幾個主要的成員
Subscription.class (訂閱資訊)
final class Subscription {
//訂閱者物件,一般情況下多為activity/fragment等
final Object subscriber;
//訂閱者方法物件,主要包括訂閱的
final SubscriberMethod subscriberMethod;
}
複製程式碼
SubscriberMethod.class (訂閱者方法物件)
public class SubscriberMethod {
//方法
final Method method;
//執行緒模式
final ThreadMode threadMode;
//事件型別
final Class<?> eventType;
//優先順序
final int priority;
//是否是粘性事件
final boolean sticky;
}
複製程式碼
FindState.class (查詢狀態,主要用來儲存查詢過程中的變數和結果)
static class FindState {
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
//事件type為key,method為value
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
//method為key,訂閱者class物件為value
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
final StringBuilder methodKeyBuilder = new StringBuilder(128);
}
複製程式碼
另外還有幾個比較重要的Map物件
- subscriptionsByEventType type為key,
CopyOnWriteArrayList<Subscription>
為value,從這裡可以看出EventBus中一個type型別可以對應很多個訂閱者。 - typesBySubscriber 則剛好相反,Subscription為key,Subscription內的所有事件type為value
- METHOD_CACHE class類物件為key,List為value,注意這裡的是SubscriberMethod,而上述的是 Subscription,
其中值得注意的是
METHOD_CACHE
採用的是ConcurrentHashMap()
這個資料模型,對於多併發做了一定的優化。
接下來我們以前看看這個強大的函式庫的內部實現原理。
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);
}
}
}
複製程式碼
register過程的主要工作
1.獲取訂閱者的類名 (比如MainActivity)
2.通過findSubscriberMethods
方法查詢訂閱者的訂閱方法
(@Subscribe註解的並且是Public修飾的)
2.1 查詢訂閱方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex屬性表示是否忽略註解器生成的MyEventBusIndex,預設情況下為false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//如果訂閱者類中沒有被 @Subscribe且public宣告的方法就會報異常
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;
}
}
複製程式碼
findUsingInfo()
List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
複製程式碼
其中以執行緒池的形式獲取FindState物件,並初始化Subscriber訂閱者物件
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
複製程式碼
正常情況下第一次使用if (findState.subscriberInfo != null)
這個判斷會為false,接下來進入findUsingReflectionInSingleClass(findState);
流程
另外需要注意的是每一次的迴圈都會呼叫findState.moveToSuperclass()
檢索父類的方法,所以對於一個訂閱者來說,子類和父類中的方法都會被檢索到,順序是子類->父類
findUsingReflectionInSingleClass()
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
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");
}
}
}
複製程式碼
- 通過放射獲取所有的類中所有的方法數
- 過濾方法 1.public修飾 2.非靜態,非抽象 3.一個引數 4.函式擁有@Subscribe的註解
findState.checkAdd(method, eventType)
校驗是否可以加入到list中
findState.checkAdd(method, eventType)
這裡分兩種情況
anyMethodByEventType()
中沒有的直接返回trueanyMethodByEventType()
中有的,做2次校驗,
這次根據 方法名>引數名進行完整校驗,因為同一個類中同名同參的函式是不存在的,而同名不同參的在前一步已經被過濾了,所以這裡確保在一個類中不會重複註冊.
但如果子類重寫父類的方法的話,就會出現相同的methodKey。這時EventBus會做一次驗證,並保留子類的訂閱資訊。由於掃描是由子類向父類的順序,故此時應當保留methodClassOld而忽略methodClass。如果程式碼上的註釋 Revert the put
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
複製程式碼
checkAdd() 之後
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
複製程式碼
新增到findState中,以一個arrayList儲存
接下來回到 getMethodsAndRelease()
方法中,return 一個方法集合
List
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;
}
複製程式碼
3.回到開頭的 register()
方法中
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
複製程式碼
相對而言註冊流程還是比較簡單的,主要是讓最開始提到的兩個map(subscriptionsByEventType和typesBySubscriber)裡面填加資料,留著事件分發時候使用。最多加一些優先順序/粘性事件的判斷。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
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);
}
}
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;
}
}
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
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);
}
}
}
複製程式碼
post流程
同樣先介紹幾個重要的類
PostingThreadState.class (事件分發狀態)
final static class PostingThreadState {
//事件佇列
final List<Object> eventQueue = new ArrayList<>();
//是否正在分發,防止併發狀態下同一個事件發出多次
boolean isPosting;
//是否在主執行緒
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
複製程式碼
ThreadLocal.class (執行緒資料的儲存類)
在指定執行緒儲存資料,資料儲存之後,只有在指定執行緒才能獲取到之前儲存過的資料。
post流程
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
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()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
複製程式碼
核心部分只有下面這句,遍歷傳送eventQueue佇列中的event,並且移除已經傳送的event
postSingleEvent(eventQueue.remove(0), postingState);
複製程式碼
postSingleEvent.class
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = 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);
}
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));
}
}
}
複製程式碼
eventInheritance
變數預設情況下被賦值為true,其中lookupAllEventTypes
函式會遍歷eventclass,得到其父類和介面的class類,事件派發的核心部分在postSingleEventForEventType()
postSingleEventForEventType.class
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;
}
複製程式碼
這個函式的邏輯也是十分簡單,根據eventClass從subscriptionsByEventType
列表中獲取訂閱者列表,接著遍歷訂閱者列表,以此將event回撥。讀了這裡時,發現將eventClass作為key,而不是event作為key,估計是因為class物件能追溯到其父類和介面實現吧。
到此,post流程也結束了,比起註冊流程還要簡單。
執行緒排程 postToSubscription()
幾種poster型別
1.mainThreadPoster 建立於HandlerPoster
HandlerPoster.class
主要變數
//佇列
private final PendingPostQueue queue;
//最大存在秒數 通常為10s,超過則會報錯,這就跟廣播的onReciver回撥10s沒處理完成就會報ANR錯誤有些類似
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
//標誌是否活動可用的
private boolean handlerActive;
複製程式碼
核心邏輯 handleMessage
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
複製程式碼
遍歷佇列,執行eventBus.invokeSubscriber(pendingPost)
方法
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);
}
}
複製程式碼
**2.BackgroundPoster:**實現 Runnable,會將PendingPostQueue佇列中所有的事件分發出去
**3.AsyncPoster:**同樣實現 Runnable,只會分發一個PendingPostQueue佇列中的事件
postToSubscription.class
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);
}
}
複製程式碼
邏輯也很好理解,相同執行緒時直接invokeSubscriber
反射回撥,不同執行緒則發到相同執行緒去回撥。
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 {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
複製程式碼
主要工作分兩步
- 移除訂閱者的所有訂閱資訊
- 移除訂閱者和其所有Event的關聯
另外看過的一篇博文上寫了有關於EventBus的優化操作,記錄下來
- eventInheritance預設為true,會遍歷class對應的父類和介面物件,如果程式中沒有使用到事件的繼承關係,可以將該值設為false
- ignoreGeneratedIndex表示是否使用生成的APT程式碼去優化查詢事件的過程,如果專案中沒有接入EventBus的APT,也可以將其設定為false
補充
hashMap.put()
hashMap.put() 會根據key返回對應的value值
如果put的時候沒有對應的key值,則新增到map中,如果有 先返回後新增
如
map.put("222", "222");
String value2=map.put("222","333");
複製程式碼
此時的value2為"222",而map中 key="222"的value為"333"
instanceof, isinstance,isAssignableFrom的區別
instanceof運算子 只被用於物件引用變數,檢查左邊的被測試物件 是不是 右邊類或介面的 例項化。如果被測物件是null值,則測試結果總是false。 形象地:自身例項或子類例項 instanceof 自身類 返回true
String s=new String("javaisland");
System.out.println(s instanceof String); //true
複製程式碼
Class類的isInstance(Object obj)方法,obj是被測試的物件,如果obj是呼叫這個方法的class或介面 的例項,則返回true。這個方法是instanceof運算子的動態等價。 形象地:自身類.class.isInstance(自身例項或子類例項) 返回true
String s=new String("javaisland");
System.out.println(String.class.isInstance(s)); //true
複製程式碼
Class類的isAssignableFrom(Class cls)方法,如果呼叫這個方法的class或介面 與 引數cls表示的類或介面相同,或者是引數cls表示的類或介面的父類,則返回true。 形象地:自身類.class.isAssignableFrom(自身類或子類.class) 返回true 例:
System.out.println(ArrayList.class.isAssignableFrom(Object.class)); //false
System.out.println(Object.class.isAssignableFrom(ArrayList.class)); //true
複製程式碼