不忘初心 砥礪前行, Tomorrow Is Another Day !
相關文章
本文概要:
- EventBus的介紹
- 簡單使用
- EventBus原始碼分析
引言
Android中訊息傳遞有多種方式.
- Handler : 執行緒間的通訊.
- BroadcastReceiver : 程式間的通訊.接收系統廣播.
- 介面回撥 : 事件觸發通知.
當我們需要在多個地方接收事件通知,此時介面回撥過於繁瑣;
廣播又顯得資源浪費.這時就需要用到EventBus了.EventBus是一種基於觀察者模式,降低元件之間耦合,簡化通訊的方式.
官方介紹地址: github.com/greenrobot/…
一. EventBus的介紹
EventBus的三劍客 :
- Publisher : 事件釋出者
- Event : 事件
- Subscriber : 事件訂閱者
事件由釋出者通過EvenentBus傳遞給訂閱者.
EventBus的四模型 :
- ThreadMode.POSTING : 在哪個執行緒釋出,就在哪個執行緒處理
- ThreadMode.MAIN : 在主執行緒處理.
- ThreadMode.BACKGROUND : 子~子 和 主~執行緒池
- ThreadMode.ASYNC : 執行緒池處理
在3.0之前只能固定的方法來指定執行緒模型:
- OnEvent
- OnEventMainThread
- OnEventBackgroundThread
- OnEventAsync
二. 簡單使用
- 定義一個事件類
public class DataEvent{
String data;
public DataEvent(String data){
this.data = data;
}
}複製程式碼
- 註冊訂閱
EventBus.getDefault().register(this);
複製程式碼
- 釋出事件
//釋出普通事件EventBus.getDefault().post(new DataEvent());
//釋出黏性事件EventBus.getDefault().postSticky(new DataEvent());
複製程式碼
- 定義訂閱方法
//指定執行緒模型和優先順序@Subscriber (threadMode = ThreadMode.MAIN,priority = 0)public void onDataEvent(DataEvent dataEvent){
}複製程式碼
- 反註冊訂閱
EventBus.getDefault().unregister(this);
&
emsp;
複製程式碼
上述示例中,使用了黏性事件和優先順序. 接下來看兩者概念.
- 黏性事件 : 事件傳送之後再進行註冊依然能收到該事件.
- 優先順序 : 預設為0,數值越大優先順序越高.
三. 基本原理
EventBus的原理主要理解註冊-釋出事件-反註冊流程三個方面.
3.1 定義註解
同樣EventBus也定義了我們需要用到的註解.
@Documented@Retention(RetentionPolicy.RUNTIME)//執行時註解@Target({ElementType.METHOD
})//修飾方法public @interface Subscribe {//定義了三個引數 ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}複製程式碼
3.2 EvenBus的註冊
先初始化:使用單例模式雙重檢查DCL方式建立EventBus物件.
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
} return instance;
}複製程式碼
初始化完成後,就需要呼叫register方法進行註冊.
public void register(Object subscriber) {
Class<
?>
subscriberClass = subscriber.getClass();
//獲取當前訂閱者所有訂閱方法(findSubscriberMethods) List<
SubscriberMethod>
subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//遍歷所有方法,將他們儲存起來(通過subscribe儲存). subscribe(subscriber, subscriberMethod);
}
}
}複製程式碼
接著看如何查到所有訂閱方法的
List<
SubscriberMethod>
findSubscriberMethods(Class<
?>
subscriberClass) {
List<
SubscriberMethod>
subscriberMethods = METHOD_CACHE.get(subscriberClass);
//如果有快取,則直接返回 if (subscriberMethods != null) {
return subscriberMethods;
} //無快取,則通過反射活編譯時生成的程式碼找到訂閱方法集合(具體實現稍後在分析,先了解到這就行) if (ignoreGeneratedIndex) {
//反射獲取 subscriberMethods = findUsingReflection(subscriberClass);
} else {
//3.0開啟索引加速 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;
}
}複製程式碼
當找到所有訂閱方法後,會儲存訂閱方法.我們看儲存的具體過程
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<
>
();
//核心map,事件型別對應的Subscription訂閱資訊. //儲存在subscriptionsByEventType(Map,key:eventType(事件型別的class物件);
// value:CopyOnWriteArrayList<
Subscription>
(Subscription集合)) 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) {
//新增單個subscription subscriptions.add(i, newSubscription);
break;
}
} List<
Class<
?>
>
subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<
>
();
//次核心map,訂閱者對應的所有事件型別 // 儲存在typesBySubscriber(Map, key:subscriber(訂閱者);
value:List<
Class<
?>
>
(事件型別的class物件集合)) 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);
}
}
} 複製程式碼
3.3 EventBus的反註冊
註冊的時候核心是對核心Map和次核心Map,進行新增操作.那麼反註冊則是刪除操作.
public synchronized void unregister(Object subscriber) {
//從次核心Map取出所有事件型別 List<
Class<
?>
>
subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<
?>
eventType : subscribedTypes) {
//從核心Map,移除訂閱資訊 unsubscribeByEventType(subscriber, eventType);
} //從次核心Map,移除訂閱者 typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
} //從核心Map,移除訂閱資訊的具體過程 private void unsubscribeByEventType(Object subscriber, Class<
?>
eventType) {
List<
Subscription>
subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
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--;
}
}
}
}複製程式碼
3.4 EventBus釋出事件
釋出事件,還是跟核心Map有很大關聯,接著看釋出事件流程.
由於Post方法內部呼叫了postSingleEvent來,其內部最終呼叫了postSingleEventForEventType傳送事件,這裡直接看該方法.
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<
?>
eventClass) {
CopyOnWriteArrayList<
Subscription>
subscriptions;
synchronized (this) {
//從核心Map,根據事件型別找到所有訂閱資訊 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;
} //postToSubscription的具體實現 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);
}
} 複製程式碼
最後我們對整個EventBus流程做一個總結.
準備工作:
先初始化,使用單例模式雙重檢查DCL方式建立EventBus物件.
註冊:
獲取當前訂閱者所有訂閱方法(findSubscriberMethods)
- 如果有快取(METHOD_CACHE),則直接返回;
無快取,則通過++反射或者編譯時生成的程式碼++來找到訂閱方法集合,然後快取起來返回.遍歷所有方法,將他們儲存起來(通過subscribe儲存).
- 組合訂閱資訊,Subscription(訂閱資訊) = Subscriber(訂閱者) + SubscriberMethods(訂閱方法)
- 儲存在事件型別對應的訂閱資訊集合的Map中.(核心Map)
- ++儲存在訂閱者對應的所有事件型別(次核心Map)++
反註冊:
- 根據當前訂閱者,從次核心Map找到所有訂閱事件型別.
- 遍歷根據訂閱事件,從核心Map找到對應所有訂閱資訊.
- 最後一次移除訂閱資訊,訂閱者.
釋出事件
- 根據當前事件型別,從核心Map找到所有訂閱資訊,遍歷呼叫.
3.4 EventBus的3.0索引加速
之前在講解EventBus的註冊過程時,只是簡單得講到獲取所有的訂閱方法,如果沒有快取時,則從反射或者編譯時註解生成的程式碼中獲取.接下來繼續看反射相關程式碼
a. 通過反射獲取所有訂閱方法
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");
}
}
}複製程式碼
b. 通過編譯時生成的程式碼獲取所有訂閱方法
private List<
SubscriberMethod>
findUsingInfo(Class<
?>
subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//獲取訂閱資訊(getSubscriberInfo方法在編譯時索引類裡) 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);
}複製程式碼
到此為止,我們知道3.0之前通過反射獲取訂閱的方法,對於效能上是有所欠缺.所以在3.0後提供了(Subscribe Index)索引加速,其實本質就是註解處理器的應用,這樣就不用通過反射了,效能上得到了很大的提高.
接下來看如何開啟索引.greenrobot.org/eventbus/do…
1. 新增註解處理器:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
//指定生成的索引檔名以及日誌列印 arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex', verbose : 'true' ]
}
}
}
} dependencies {
implementation 'org.greenrobot:eventbus:3.1.1' annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}複製程式碼
最後我們在MainActivity準備一些訂閱方法方法
@Subscribe(threadMode = ThreadMode.MAIN, priority = 0) public void onDataEventOne(Integer dataEvent) {
} @Subscribe(threadMode = ThreadMode.MAIN, priority = 1) public void onDataEventTwo(String dataEvent) {
}複製程式碼
接著編譯下專案,此時在build-generated-source-apt的包下,通過註解處理器生成了我們自己定義的索引檔案MyEventBusIndex.java
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<
Class<
?>
, SubscriberInfo>
SUBSCRIBER_INDEX;
static {
//索引Map,key為訂閱者,value為訂閱者資訊 SUBSCRIBER_INDEX = new HashMap<
Class<
?>
, SubscriberInfo>
();
//新增訂閱者資訊到Map中 putIndex( // new SimpleSubscriberInfo(org.jasonhww.eventbusdemo.MainActivity.class, true, new SubscriberMethodInfo[] {
//建立訂閱方法物件 new SubscriberMethodInfo("onDataEventOne", Integer.class, ThreadMode.MAIN), new SubscriberMethodInfo("onDataEventTwo", String.class, ThreadMode.MAIN, 1, false),
}));
} //將訂閱資訊新增到索引Map中 private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
} //獲取訂閱者對應的訂閱資訊 //此方法就在註冊查詢所有訂閱方法的具體實現. @Override public SubscriberInfo getSubscriberInfo(Class<
?>
subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}複製程式碼
接著在初始化EventBus時新增我們配置好的索引.
//方法一,使用自定義的EventBusEventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
//方法二,使用預設的EventBus,大多數情況下采取這種就行.EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
EventBus eventBus = EventBus.getDefault();
複製程式碼
2. 註解處理器-建立索引類
在之前分析編譯時註解和ButterKnife,都有講到註解處理器的運用.同樣我們依葫蘆畫瓢來看EventBus是如何利用註解處理器去生成我們的索引類的.
相比之前分析的,這裡註解處理器類上多了一個@SupportedOptions(value = {“eventBusIndex”, “verbose”
})配置,這裡對應了最開始gradle的配置.
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")@SupportedOptions(value = {"eventBusIndex", "verbose"
})複製程式碼
這裡直接看註解處理器process方法裡,最重要的兩步.
- 收集資訊
待生成的java檔案資訊儲存在methodsByClass中.
private void collectSubscribers(Set<
? extends TypeElement>
annotations, RoundEnvironment env, Messager messager) {
for (TypeElement annotation : annotations) {//遍歷所有註解類對應的TypeElement //獲取被註解的元素(如MainActivity的onDataEventOne方法對應的元素物件) Set<
? extends Element>
elements = env.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
if (checkHasNoErrors(method, messager)) {
//獲取被註解所在的類 TypeElement classElement = (TypeElement) method.getEnclosingElement();
//存入Map中,生成檔案時呼叫. methodsByClass.putElement(classElement, method);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
}
}
}
}複製程式碼
- 生成java檔案
與ButterKnife不同,這裡使用了JDK的JavaFileObject建立索引類.可以對照之前生成的索引類.
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
String myPackage = period >
0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write("package " + myPackage + ";
\n\n");
} writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;
\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;
\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;
\n\n");
writer.write("import java.util.HashMap;
\n");
writer.write("import java.util.Map;
\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map<
Class<
?>
, SubscriberInfo>
SUBSCRIBER_INDEX;
\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap<
Class<
?>
, SubscriberInfo>
();
\n\n");
//此裡面呼叫收集資訊時存入的Map(methodsByClass) writeIndexLines(writer, myPackage);
writer.write("
}\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
\n");
writer.write("
}\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class<
?>
subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;
\n");
writer.write("
} else {\n");
writer.write(" return null;
\n");
writer.write("
}\n");
writer.write("
}\n");
writer.write("
}\n");
} catch (IOException e) {
throw new RuntimeException("Could not write source for " + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}複製程式碼
這樣就成功的將訂閱者的所有訂閱方法都儲存在了索引檔案了,這樣最終註冊的時候就不需要再利用反射區查詢訂閱方法了.
最後還是老規矩做一個小的總結.
所謂索引加速,就是利用註解處理器通過解析註解資訊,生成java檔案的索引類,將所有訂閱的方法儲存在索引類而已.
由於本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.
參考連結: