不忘初心 砥礪前行, Tomorrow Is Another Day !html
本文概要:java
Android中消息傳遞有多種方式.android
當咱們須要在多個地方接收事件通知,此時接口回調過於繁瑣;廣播又顯得資源浪費.這時就須要用到EventBus了. EventBus是一種基於觀察者模式,下降組件之間耦合,簡化通訊的方式.
官方介紹地址: github.com/greenrobot/…git
EventBus的三劍客 :github
事件由發佈者經過EvenentBus傳遞給訂閱者.緩存
EventBus的四模型 :bash
在3.0以前只能固定的方法來指定線程模型:app
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); 
複製代碼
上述示例中,使用了黏性事件和優先級. 接下來看二者概念.async
EventBus的原理主要理解註冊-發佈事件-反註冊流程三個方面.ide
一樣EventBus也定義了咱們須要用到的註解.
@Documented
@Retention(RetentionPolicy.RUNTIME)//運行時註解
@Target({ElementType.METHOD})//修飾方法
public @interface Subscribe {
//定義了三個參數
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
複製代碼
先初始化:使用單例模式雙重檢查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);
}
}
}
複製代碼
註冊的時候核心是對核心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--;
}
}
}
}
複製代碼
發佈事件,仍是跟核心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找到全部訂閱信息,遍歷調用.
以前在講解EventBus的註冊過程時,只是簡單得講到獲取全部的訂閱方法,若是沒有緩存時,則從反射或者編譯時註解生成的代碼中獲取.接下來繼續看反射相關代碼
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");
}
}
}
複製代碼
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…
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時添加咱們配置好的索引.
//方法一,使用自定義的EventBus
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
//方法二,使用默認的EventBus,大多數狀況下采起這種就行.
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
EventBus eventBus = EventBus.getDefault();
複製代碼
在以前分析編譯時註解和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);
}
}
}
}
複製代碼
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文件的索引類,將全部訂閱的方法保存在索引類而已.
因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.
參考連接: