EventBus源碼分析(二)
在之前的一篇文章EventBus源碼分析(一)分析了EventBus關(guān)于注冊注銷以及事件分發(fā)的機(jī)制,而關(guān)于注冊就是當(dāng)一個類注冊監(jiān)聽EventBus發(fā)出的事件時,EventBus負(fù)責(zé)找出該類所有的監(jiān)聽事件的方法并保存在EventBus內(nèi)部的相應(yīng)數(shù)據(jù)結(jié)構(gòu)中,具體地講就是subscriptionsByEventType, typesBySubscriber, stickyEvents,三個Map的數(shù)據(jù)結(jié)構(gòu)中。從EventBus 3.0開始,訂閱方法使用注解@Subscribe標(biāo)注,我們都知道對于注解的處理需要使用注解處理器,最簡答的方法就是反射的方式,在運行期獲取訂閱方法信息,但是這樣會影響性能。另一方面,Java提供了apt工具可以對Java源碼在編譯期做處理,從而避免了對于運行期性能的影響,這不過這種方式稍微有些復(fù)雜,而且會生成代碼,尤其是對于Android系統(tǒng),應(yīng)用方法數(shù)是受限的,生成的代碼會增長應(yīng)用的方法數(shù),所以兩種方式各有利弊。EventBus也提供了兩種方式用于查找訂閱方法信息,下面分為兩部分介紹。
1. 總述
在上一篇文章中介紹EventBus源碼中提到訂閱方法的查找是使用SubscriberMethodFinder的List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)方法,從方法簽名中可以看出,其功能就是注冊類,即Subscriber在注冊時找出該類中所有的訂閱方法,然后返回包含訂閱方法信息的列表。下面直接看該方法的源碼:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//緩存處理
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//根據(jù)開關(guān)分情況處理
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
} else {
//處理緩存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
其中ignoreGeneratedIndex是一個boolean值的開關(guān),由SubscriberMethodFinder構(gòu)造器傳遞進(jìn)來的參數(shù)決定,根據(jù)其值決定是使用反射還是適應(yīng)index文件做訂閱方法的處理。另外METHOD_CACHE是一個緩存,Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();是一個線程安全的Map, 用來存儲已經(jīng)查找到的某些類的訂閱方法信息,在重復(fù)訂閱時可以提高性能。
在介紹兩種方式查找訂閱方法之前,首先需要了解一個內(nèi)部類,類似于事件分發(fā)中PostingThreadState用于記錄線程分發(fā)事件的狀態(tài),在查找訂閱方法過程中也有一個FindState用于記錄查找的狀態(tài),我們首先看其內(nèi)部的屬性變量:
static class FindState {
final List<SubscriberMethod> subscriberMethods = new ArrayList<>(); //記錄查找得到的訂閱方法信息
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
final StringBuilder methodKeyBuilder = new StringBuilder(128);
Class<?> subscriberClass; //訂閱者類的class對象
Class<?> clazz; //保存class對象信息的臨時成員變量,用于存放訂閱者類的父類的class對象
boolean skipSuperClasses; //是否查找父類中的訂閱方法
SubscriberInfo subscriberInfo; //訂閱者的信息,在非反射的方式中會用到,后面再做解釋
屬性變量中包含一些存儲信息的數(shù)據(jù)結(jié)構(gòu),已經(jīng)用注釋說明,其中anyMethodByEventType,subscriberClassByMethodKey,methodKeyBuilder,是用在當(dāng)一個訂閱者類中同時有多個方法訂閱同一個事件類型時用到,稍后做出解釋。下面我們看FindState中的一些重要方法:
void initForSubscriber(Class<?> subscriberClass) {
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}
void recycle() {
subscriberMethods.clear();
anyMethodByEventType.clear();
subscriberClassByMethodKey.clear();
methodKeyBuilder.setLength(0);
subscriberClass = null;
clazz = null;
skipSuperClasses = false;
subscriberInfo = null;
}
這兩個方法不需要解釋,簡單的初始化以及清理回收。下面為兩個重要方法,檢查一個訂閱方法是否可以加入到訂閱方法信息中,即檢查@Subscribe標(biāo)注的方法是否正確,其代碼為:
boolean checkAdd(Method method, Class<?> eventType) {
// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
// Usually a subscriber doesn't have methods listening to the same event type.
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
return true;
} else {
if (existing instanceof Method) {
if (!checkAddWithMethodSignature((Method) existing, eventType)) {
// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
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;
}
}
正如剛開始的注釋所言,通常一個類中只有一個方法監(jiān)聽一個事件,這個很普遍驗證方式也很簡單,只會走第一個if語句,然后返回的true,此時anyMethodByEventType保存了事件類型和其對應(yīng)的訂閱方法。復(fù)雜出現(xiàn)在同一個類中有多個方法監(jiān)聽同一個事件類型,當(dāng)監(jiān)聽一個事件的第二個方法進(jìn)來檢查的時候,existing就是第一個存進(jìn)去的方法,這時候就要啟動二級檢查,即checkAddWithMethodSignature,如其名使用方法簽名檢查。在第二個方法檢查之前需要首先檢查第一個存進(jìn)去的方法,因為需要將所有的方法簽名存起來用于比較。執(zhí)行完這一步之后,anyMethodByEventType中該事件類型對應(yīng)的值改成了該FindState對象,不再是method,所以當(dāng)?shù)谌齻€第四個方法進(jìn)來檢查時無需檢查之前的方法,新進(jìn)來的方法直接進(jìn)入方法簽名的檢查。
下面看checkAddWithMethodSignature,首先是methodKeyBuilder構(gòu)造一個方法簽名,構(gòu)造方式很簡單,我們只需要知道字符串可以唯一標(biāo)識一個方法即可,然后是獲取該方法所屬的類,注意getDeclaringClass()是獲取該方法所屬的類,與繼承關(guān)系無關(guān),比如A類聲明方法a(), B 繼承A 并覆寫a()方法,如果是在B注冊時查找方法信息,此時的method對象所屬的類是B,而不是A。最后就是以方法簽名為鍵值,類的class對象為值存到subscriberClassByMethodKey數(shù)據(jù)結(jié)構(gòu)中,并檢查該方法是否可以存儲到訂閱方法信息中,檢查的邏輯,同樣是方法簽名不同時也很簡單,methodClassOld為空,直接允許并保存信息,如果methodClassOld不為空,那么methodClassOld與methodClass肯定是不同的類,否則編譯會有錯誤。那么在一個類注冊的時候EventBus只會遍歷它和它的父類,即methodClass是methodClassOld的父類,此時返回false,并且subscriberClassByMethodKey中該方法簽名對應(yīng)的值還是保存methodClassOld,即保持其處于繼承關(guān)系的最下層,這個也符合情理,在子類注冊監(jiān)聽事件時,父類中相同的方法不應(yīng)該被調(diào)用,而是應(yīng)該調(diào)用子類的覆寫方法,之所以遍歷父類是考慮父類中注冊的其他事件,因為父類監(jiān)聽的事件,子類應(yīng)當(dāng)是繼承的。但是這里有一點疑問,如果methodClass是methodClassOld的子類,我們很快會看到FindState中只有moveToSuperclass方法,后進(jìn)來的class對象只可能是父類,不可能出現(xiàn)子類,這里添加methodClassOld.isAssignableFrom(methodClass)這個條件是為了邏輯的完備性還是為了其他我還沒有相當(dāng)?shù)那闆r,對此還不清楚,歡迎熟悉這塊的讀者在評論中告知。
最后FindState中還有剛剛提到的moveToSuperclass方法,代碼如下:
void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
/** Skip system classes, this just degrades performance. */
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
clazz = null;
}
}
}
在一次注冊過程中,查找訂閱信息始終保持一個FindState對象,clazz作為臨時變量始終是當(dāng)前所查詢的subscriberClass的本身或某一級父類。這里跳過了Java系統(tǒng)的類以及Android系統(tǒng)的類,因為這些類我們是不可更改的,肯定不會包含訂閱方法的。
FindState是一個很重要的數(shù)據(jù)結(jié)構(gòu),存儲在查找過程的一些狀態(tài)以及查詢結(jié)果信息,這里介紹這個內(nèi)部類,剛開始可能會不太明白,不過可以先看下面的反射方式的查找過程,再回頭看此內(nèi)部類,或許會容易理解一些。FindState到此已經(jīng)介紹完了,下面分為反射和非反射兩種方式介紹訂閱方法查找的過程。
2. 反射方式
在運行期使用反射方式特點雖然會影響運行時的性能。但是其過程比較簡單,所以首先介紹反射方式的查找過程,其代碼如下:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass); //初始化FindState對象
while (findState.clazz != null) { //遍歷subscriberClass以及其父類的class對象
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState); //根據(jù)findState對象返回查找結(jié)果
}
private FindState prepareFindState() { //FIND_STATE_POOL這里使用緩存,避免了對象的創(chuàng)建與銷毀
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
return new FindState();
}
findUsingReflection方法的邏輯很簡單,已經(jīng)在注釋中說明,不過值得注意的是prepareFindState方法中,使用緩存池保存FindState對象,避免了對象的創(chuàng)建和銷毀,從而避免引發(fā)內(nèi)存抖動。下面重點分析findUsingReflectionInSingleClass方法,其代碼為:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
//1. 獲取SubScriber中的所有方法
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; //由于對反射不是很熟悉,這里為什么還不明白,之后會看issues/149
}
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) { //訂閱方法必須是只有一個參數(shù)
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) { //檢查該訂閱方法是否正確,檢查規(guī)則第一部分已經(jīng)說明
ThreadMode threadMode = subscribeAnnotation.threadMode();
//創(chuàng)建SubscriberMethod(該類已經(jīng)在第一篇文章中介紹)對象,并添加到FindState的subscriberMethods中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
//strictMethodVerification是構(gòu)造器傳遞進(jìn)來的開關(guān)參數(shù),只有為true時,才會向用戶通知方法的異常
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");
}
}
}
該方法的邏輯也不復(fù)雜,關(guān)鍵步驟在注釋中已經(jīng)說明,無非是一些檢查條件,公有非抽象非靜態(tài)方法而且必須是只帶有有個參數(shù)的方法,此時可以再回去看看checkAdd以及checkAddWithSignature,再思考一下一個類的訂閱方法覆寫父類的方法時會出現(xiàn)一些較為復(fù)雜的情況。此外,MODIFIERS_IGNORE
/*
* In newer class files, compilers may add methods. Those are called bridge or synthetic methods.
* EventBus must ignore both. There modifiers are not public but defined in the Java class file format:
* http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1
*/
private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
表示非抽象非靜態(tài),至于BRIDGE和SYNTHETIC是EventBus中自己定義的類型,表示編譯器添加的方法,對Java虛擬機(jī)不是很熟悉,對于編譯器添加的方法也不太明白,這里只需要理解為我們手動定義的,公有非靜態(tài)非抽象方法即可。
最后看一下在查找過程結(jié)束以后,從FindState對象中返回查找信息的方法:
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;
}
這里需要注意的是使用ArrayList包裝findState.subscriberMethods,是為了深拷貝,如果是直接賦值,在recycle之后,返回信息也會被清空,這一點大家都知道,卻很容易忽略。另外,這里值得學(xué)習(xí)的是與剛開始對應(yīng)的,在recycle之后將對象保存到緩存池中,重復(fù)利用。
至此,反射方式的查找過程就介紹完了,這種方式比較簡單,重點是findUsingReflectionInSingleClass方法,這種方式簡單卻影響運行時的性能,與Java中編譯期處理源碼的方式一樣,EventBus也提供了index方法,在編譯期處理源碼,生成有效的查找方法代碼,避免了運行期的反射。下面介紹index方式的查詢過程。
3. index 方式
既然不是在運行期使用反射技術(shù)查詢方法信息,那么肯定需要在編譯期對源碼做處理,EventBus使用了自定義的Subscribe注解,那么肯定需要自己實現(xiàn)注解處理器,所以首先來看注解處理器的實現(xiàn)。
注解處理器,以及其process方法的主要代碼為:
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex";
public static final String OPTION_VERBOSE = "verbose";
/** Found subscriber methods for a class (without superclasses). */
private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>(); //記錄訂閱方法信息
private final Set<TypeElement> classesToSkip = new HashSet<>(); //記錄需要跳過的subscriber
private boolean writerRoundDone;
private int round;
private boolean verbose;
...
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
try {
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
...
verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
int lastPeriod = index.lastIndexOf('.');
String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;
round++;
...
collectSubscribers(annotations, env, messager);//收集訂閱方法信息保存在methodsByClass
checkForSubscribersToSkip(messager, indexPackage);//檢查需要跳過的類,即subscriber, 保存在classesToSkip
if (!methodsByClass.isEmpty()) {
createInfoIndexFile(index); //創(chuàng)建生成的Java代碼文件
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
...
} catch (RuntimeException e) {
...
}
return true;
}
....
}
這里讀代碼做了簡化,出去了一些錯誤的處理以及錯誤信息的輸出,主要是由于對注解處理器不太熟悉,一些邏輯不明白其中的道理,為了避免分析出錯,貽笑大方,所以這里只簡單介紹其中的流程,有興趣的可以自行查看源碼。
這里首先獲取參數(shù)中的index,它是要生成類的全稱,從中獲取包名indexPackage, round++則是遞歸地處理源碼中的注解,即生成的源碼中如果還有注解則遞歸處理,接下來就是重要的三個方法,如代碼中添加的注釋,分別負(fù)責(zé)收集訂閱方法信息,檢查需要跳過的類,最后生成Java代碼文件。
下面首先看collectSubscribers,其代碼為:
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
for (TypeElement annotation : annotations) {
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();
methodsByClass.putElement(classElement, method);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
}
}
}
}
邏輯很簡單,既然subscribe注解只是標(biāo)注方法,所以只需要處理ExecutableElement,然后檢查方法的正確性,即checkHasNoErrors,這里不再貼出源碼,其邏輯就是檢查是否為公有非靜態(tài)非抽象的方法,這個與之前所說的一致。方法正確,則將該方法所屬的類和該方法保存到methodByClass中,這里是ListMap, 所以鍵值是表示該類的TypeElement, 值則為ExecutableElement的方法的List。
下面是
/**
* Subscriber classes should be skipped if their class or any involved event class are not visible to the index.
*/
private void checkForSubscribersToSkip(Messager messager, String myPackage) {
//遍歷檢查所有添加到methodsByClass中的類
for (TypeElement skipCandidate : methodsByClass.keySet()) {
TypeElement subscriberClass = skipCandidate;
while (subscriberClass != null) {
// 檢查類是否為public,非公有則加入到classesToSkip,不需要再檢查方法
if (!isVisible(myPackage, subscriberClass)) {
boolean added = classesToSkip.add(skipCandidate);
if (added) {
String msg;
if (subscriberClass.equals(skipCandidate)) {
msg = "Falling back to reflection because class is not public";
} else {
msg = "Falling back to reflection because " + skipCandidate +
" has a non-public super class";
}
messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
}
break;
}
//如果類是public的,則檢查Event類型是否為可以處理,并且是public的
List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
if (methods != null) {
for (ExecutableElement method : methods) {
String skipReason = null;
VariableElement param = method.getParameters().get(0);
TypeMirror typeMirror = getParamTypeMirror(param, messager);
if (!(typeMirror instanceof DeclaredType) ||
!(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
skipReason = "event type cannot be processed";
}
if (skipReason == null) {
TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
if (!isVisible(myPackage, eventTypeElement)) {
skipReason = "event type is not public";
}
}
if (skipReason != null) {
boolean added = classesToSkip.add(skipCandidate);
if (added) {
String msg = "Falling back to reflection because " + skipReason;
if (!subscriberClass.equals(skipCandidate)) {
msg += " (found in super class for " + skipCandidate + ")";
}
messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
}
break;
}
}
}
//遞歸遍歷其父類
subscriberClass = getSuperclass(subscriberClass);
}
}
}
這個方法稍微有些長,但是邏輯很清晰,內(nèi)部循環(huán)總共分為三個部分,注釋已經(jīng)表明,其中isVisible()方法是判斷這個類對于indexPackage,即index所定義的包是否為可見的,其代碼邏輯比較簡單不再貼出,通常來說如果不處于一個包內(nèi),這個受檢查的類必須是public, 否則就是不可見的,需要加入跳過的類的數(shù)據(jù)結(jié)構(gòu)中,從代碼中可以看出如果訂閱方法所屬的類或者該類中有一個方法的Event不可處理,即Event不是public的,(至于DeclaredType,不太清楚),那么該類中的所有訂閱方法都不會在生成的Java代碼文件中出現(xiàn)。
最后是代碼生成的方法,其代碼都是一些寫入邏輯,這里做了簡化,有興趣的可以自行查閱:
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( ...
...
這些代碼看著比較抽象,這里舉一個簡單的例子,在自己的工程中做如下配置:
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.recluse.MyEventBusIndex"
}
}
然后,自己的MainActivity如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void getEvent(DefaultEvent event){
}
}
此時編譯程序,會在build->generated->apt->debug文件夾下生成一個文件,其代碼為:
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(com.example.androidlearning.MainActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("getEvent", com.example.androidlearning.DefaultEvent.class, ThreadMode.MAIN),
}));
}
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;
}
}
}
這段代碼就是createInfoIndexFile方法生成,編譯過后就可以在運行期調(diào)用生成代碼的方法用于查詢訂閱方法的信息。這里需要注意MainActivity的注冊方法那行代碼是注釋掉的,也就是說即使沒有注冊EventBus,只要有@Subscribe標(biāo)注的方法,注解處理器就會為我們生成對應(yīng)的代碼。這段代碼此處暫時不分析,后面會解釋其中的邏輯。
以上部分就是注解處理器部分,注解處理器會收集所有的訂閱方法信息,然后生成對應(yīng)的Java代碼源文件,編譯一同進(jìn)入可執(zhí)行的文件中,這樣就可以在運行期直接進(jìn)行調(diào)用查詢對應(yīng)的訂閱方法。下面就開始重點,即如何查詢訂閱方法。
在介紹查詢方法之前,我們需要熟悉幾個定義的接口,先從生成代碼所實現(xiàn)的接口開始:
/**
* Interface for generated indexes.
*/
public interface SubscriberInfoIndex {
SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}
這個接口定義的功能是可以通過訂閱的類查找到該類所有的訂閱方法,這個就是我們最終想要的,先定義出來,由生成代碼實現(xiàn),那么很容易就能猜到所有訂閱方法的信息肯定是保存在返回的SubscriberInfo中,這個也是一個接口,其代碼為:
/** Base class for generated index classes created by annotation processing. */
public interface SubscriberInfo {
Class<?> getSubscriberClass();
SubscriberMethod[] getSubscriberMethods();
SubscriberInfo getSuperSubscriberInfo();
boolean shouldCheckSuperclass();
}
從這里就可以看出來了,前兩個方法就是我們想要的,后面兩個方法則是處理繼承關(guān)系的。下面就是該方法的抽象實現(xiàn):
/** Base class for generated subscriber meta info classes created by annotation processing. */
public abstract class AbstractSubscriberInfo implements SubscriberInfo {
private final Class subscriberClass;
private final Class<? extends SubscriberInfo> superSubscriberInfoClass;
private final boolean shouldCheckSuperclass;
protected AbstractSubscriberInfo(Class subscriberClass, Class<? extends SubscriberInfo> superSubscriberInfoClass,
boolean shouldCheckSuperclass) {
this.subscriberClass = subscriberClass;
this.superSubscriberInfoClass = superSubscriberInfoClass;
this.shouldCheckSuperclass = shouldCheckSuperclass;
}
@Override
public Class getSubscriberClass() {
return subscriberClass;
}
@Override
public SubscriberInfo getSuperSubscriberInfo() {
if(superSubscriberInfoClass == null) {
return null;
}
try {
return superSubscriberInfoClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean shouldCheckSuperclass() {
return shouldCheckSuperclass;
}
protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType) {
return createSubscriberMethod(methodName, eventType, ThreadMode.POSTING, 0, false);
}
protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode) {
return createSubscriberMethod(methodName, eventType, threadMode, 0, false);
}
protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode,
int priority, boolean sticky) {
try {
Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
} catch (NoSuchMethodException e) {
throw new EventBusException("Could not find subscriber method in " + subscriberClass +
". Maybe a missing ProGuard rule?", e);
}
}
}
這個抽象類實現(xiàn)了接口,而接口中四個方法中實現(xiàn)了三個,而實現(xiàn)邏輯就是返回構(gòu)造器傳進(jìn)來的值,所以邏輯很簡單,只有一個我們最為需要的方法,就是getSubscriberMethods留個了子類實現(xiàn),同時它實現(xiàn)了createSubscriberMethod方法,實現(xiàn)了訂閱信息(即該方法的幾個參數(shù))轉(zhuǎn)換為SubscriberMethod對象,這樣子類可以很方便地調(diào)用。最后就是EventBus為我們提供的一個實現(xiàn)類,就是在生成代碼中用到的SimpleSubscriberInfo,其代碼為:
/**
* Uses {@link SubscriberMethodInfo} objects to create {@link org.greenrobot.eventbus.SubscriberMethod} objects on demand.
*/
public class SimpleSubscriberInfo extends AbstractSubscriberInfo {
private final SubscriberMethodInfo[] methodInfos;
//構(gòu)造器,傳入SubscriberInfo接口定義的四個功能中所需要所有數(shù)據(jù),只不過SubscriberMethod需要SubscriberMethodInfo轉(zhuǎn)變一下
public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
super(subscriberClass, null, shouldCheckSuperclass);
this.methodInfos = methodInfos;
}
//我們所需要的方法,也就是簡單地將SubscriberMethodInfo轉(zhuǎn)變?yōu)镾ubscriberMethod
@Override
public synchronized SubscriberMethod[] getSubscriberMethods() {
int length = methodInfos.length;
SubscriberMethod[] methods = new SubscriberMethod[length];
for (int i = 0; i < length; i++) {
SubscriberMethodInfo info = methodInfos[i];
methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
info.priority, info.sticky);
}
return methods;
}
}
這個類的邏輯已經(jīng)在注釋中解釋,但是有一點需要注意,就是調(diào)用父類構(gòu)造器時,即AbstractSubscriberInfo的構(gòu)造器,有一個參數(shù),就是superSubscriberInfoClass,即Subscriber的父類的class對象,它傳的空值。
到這里我們就清楚了,注解處理器中收集到的所有有關(guān)訂閱方法的信息都以SubscriberMethodInfo的形式保存下來,然后由SimpleSubscriberInfo將其轉(zhuǎn)變?yōu)槲覀兿胍腟ubscriberMethod?,F(xiàn)在再來分析我們前面沒有分析的生成代碼:
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(com.example.androidlearning.MainActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("getEvent", com.example.androidlearning.DefaultEvent.class, ThreadMode.MAIN),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
這里使用靜態(tài)的SUBSCRIBER_INDEX保存每個類的SubscriberInfo,而注解處理器收集到的信息都在SimpleSubscriberInfo構(gòu)造器中傳遞進(jìn)去構(gòu)造出我們想要的SubscriberInfo,保存在SUBSCRIBER_INDEX中。在接口定義的方法:
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
只是簡答地從Map中查詢相應(yīng)的SubscribeInfo。至此生成代碼也就分析完了,到這里我們也就大概可以猜出SubscriberMethodFinder是如何使用index方式查詢訂閱信息了,它首先需要獲取一個index類的對象,然后使用subscriberClass查找對應(yīng)的SubscriberInfo, 然后查找對應(yīng)的List<SubscriberMehtod>。下面看SubscriberMethodFinder中的代碼:
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
//初始化findState
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//根據(jù)findState開始查詢
while (findState.clazz != null) {
//獲取SubscriberInfo,這個是重點
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
//檢查所有的方法,并添加到findState.subscriberMethods,其邏輯與使用反射方式相同
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//如果沒有找到該類對應(yīng)的SubscriberInfo,則還是使用反射方式
findUsingReflectionInSingleClass(findState);
}
//遍歷父類
findState.moveToSuperclass();
}
//獲取訂閱信息并釋放findState
return getMethodsAndRelease(findState);
}
這段代碼邏輯很清晰就不在解釋了,除了getSubscriberInfo(findState)方法之外,其邏輯與反射方式中幾乎完全一致,下面重點看getSubscriberInfo()方法:
private SubscriberInfo getSubscriberInfo(FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
這個方法后面一半很容易理解,subscriberInfoIndexes是SubscriberFinder構(gòu)造器傳進(jìn)來的值,其代表生成代碼的類,也就是用于查詢SubscriberInfo的類,在編譯期生成,而EventBus類中subscriberInfoIndexes是Builder中可以添加的參數(shù),所以就明白了我們?nèi)绻胍褂胕ndex方式,需要在生成EventBus對象時添加該參數(shù),官方實例如下:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
如果想要使用默認(rèn)的單例,還可以使用如下方式:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
這樣講index的類傳進(jìn)去,就可以使用生成的類,用于查詢訂閱信息了。
接下來看getSubscriberInfo方法中的前一半邏輯,我們之前說過SimpleSubscriberInfo構(gòu)造器沒有SuperSubscriberClass這個參數(shù),而它在調(diào)用父類構(gòu)造器時也是傳的空,在看AbstractSubscriberInfo中,getSuperSubscriberInfo()方法如下:
@Override
public SubscriberInfo getSuperSubscriberInfo() {
if(superSubscriberInfoClass == null) {
return null;
}
try {
return superSubscriberInfoClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
如果我們使用EventBus默認(rèn)的index方式,superSubscriberInfoClass必然為空,那么也就永遠(yuǎn)不會執(zhí)行上面那個方法的前一半邏輯,所以需要自己實現(xiàn)SubscriberInfo時才會執(zhí)行,而findState.clazz == superclassInfo.getSubscriberClass()仿佛又是在判斷到達(dá)了類結(jié)構(gòu)的頂端或者類似的,一時沒有想明白,知道部分用意的讀者歡迎在評論中告知,不勝感激!
好了,到此index方式查詢方法也介紹完了,總結(jié)就是如下步驟,第一步注解處理器收集subscribe注解標(biāo)注的方法,將信息寫入生成的代碼中,并以subscriberClass為鍵,以SubscribeInfo為值保存信息;第二步就是在一個類注冊時,使用該類在EventBus的所有index類中查詢對應(yīng)的SubscribeInfo, 進(jìn)而可以獲取到訂閱方法信息,使用訂閱方法信息就可以執(zhí)行注冊操作(這部分在第一篇文章中講述)。
而整個查找過程中,無論是使用反射方式還是index方式,都是需要動態(tài)更新一個FindState的內(nèi)部類的對象,該對象記錄查詢狀態(tài)并記錄查詢結(jié)果,最后對其回收重復(fù)利用。
4. 后記
本篇文章主要介紹了EventBus查詢訂閱方法信息的過程,重點是講述了index方式的查找,由于對注解處理器了解較少,對于注解處理器的講述比較粗糙簡略,而且還會有一些錯誤,歡迎批評指正,有時間還是需要看一些java的基礎(chǔ)。EventBus源碼分析的第二部分就結(jié)束了,接下來就需要學(xué)習(xí)EventBus最核心的代碼了,就是它的三個Poster,也就是這三個Poster是的事件可以在不同線程中特別方便地傳遞,使得應(yīng)用極為簡單,這部分將在第三部分做分析。