網上關于EventBus的分析已經很多,尤其是EventBus的訂閱以及事件發送、接收相關的內容。這里不分析該部分的內容,僅分析一下索引文件是如何生成的。
ps:本文源碼分析基于EventBus 3.3.1
涉及到的類
EventBus索引文件生成涉及的類
使用方法
// build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// 配置索引文件名
arguments = [eventBusIndex: 'com.xxx.yyy.HelloEventBusIndex']
}
}
}
}
// xxx.java
EventBus.builder().addIndex(new com.xxx.yyy.HelloEventBusIndex()).build();
或
EventBus.builder().addIndex(new com.xxx.yyy.HelloEventBusIndex()).installDefaultEventBus();
索引文件的生成
// 此注解處理器支持的注解類型是"org.greenrobot.eventbus.Subscribe"
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe”)
// 此注解處理器支持的參數
@SupportedOptions(value = {"eventBusIndex", "verbose”})
// 用于構建增量注解處理器的注解,幫助我們生成了 /META-INF/gradle/注解 目錄和文件的功能,相關可參考https://github.com/tbroyer/gradle-incap-helper
@IncrementalAnnotationProcessor(AGGREGATING)
public class EventBusAnnotationProcessor extends AbstractProcessor {
// build.gradle中eventBus配置的arguments的key
// eventBus索引文件的key,通知該key可獲取對應的索引文件名(全限定名,即包含包路徑),該索引文件是SubscriberInfoIndex的子類
public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex”;
// 是否通過Messager打印相關日志,后面日志相關的代碼省略
public static final String OPTION_VERBOSE = "verbose”;
// 獲取訂閱類的所有訂閱方法(不包含其父類),key是訂閱類對應的TypeElement,value是訂閱類的所有訂閱方法對應的ExecutableElement的集合
private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
// 無效訂閱類對應的TypeElement集合
private final Set<TypeElement> classesToSkip = new HashSet<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
try {
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
if (index == null) {
// build.gradle中沒有設置eventBusIndex
return false;
}
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);
// 檢查無效的訂閱類和方法
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {
// 創建索引文件并輸出內容
createInfoIndexFile(index);
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
writerRoundDone = true;
} catch (RuntimeException e) {
// ....
}
return true;
}
// 收集訂閱類和方法
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
for (TypeElement annotation : annotations) {
// 1.獲取被@Subscribe修飾的元素
Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
// 2.如果該元素是方法(ExecutableElement表示類或者接口中的方法,構造函數或者初始化器)
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
// 3.如果方法是public 且 不是static 且 方法參數只有一個,則是有效方法
if (checkHasNoErrors(method, messager)) {
// Element.getEnclosingElement()獲取Element的父元素
// 4.獲取method元素的父元素,即類(TypeElement)
TypeElement classElement = (TypeElement) method.getEnclosingElement();
// 5. 將訂閱方法對應的元素放入對應的集合中
methodsByClass.putElement(classElement, method);
}
}
}
}
}
// 檢測方法是否合法
private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
// 1.訂閱方法是否static
if (element.getModifiers().contains(Modifier.STATIC)) {
return false;
}
// 2.訂閱方法是否public
if (!element.getModifiers().contains(Modifier.PUBLIC)) {
return false;
}
// 3.訂閱方法參數是否只有一個
List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
if (parameters.size() != 1) {
return false;
}
return true;
}
// 檢查無效的訂閱類和方法
private void checkForSubscribersToSkip(Messager messager, String myPackage) {
// 1.遍歷訂閱類
for (TypeElement skipCandidate : methodsByClass.keySet()) {
TypeElement subscriberClass = skipCandidate;
while (subscriberClass != null) {
// 2.如果訂閱類對于索引類不可見,則添加到classesToSkip集合中
// 無效的條件:如果訂閱類及其父類不是puhlic的 或 訂閱類及其父類的包名和eventBusIndex的包名不一樣,則該訂閱類無效,需要忽略
if (!isVisible(myPackage, subscriberClass)) {
boolean added = classesToSkip.add(skipCandidate);
// ...
break;
}
List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
if (methods != null) {
// 3.遍歷訂閱方法,如果方法參數不合法則添加到classesToSkip集合中
for (ExecutableElement method : methods) {
String skipReason = null;
VariableElement param = method.getParameters().get(0);
// 獲取參數類型
TypeMirror typeMirror = getParamTypeMirror(param, messager);
// 3.1 如果方法參數類型不是一個類或接口類型,則無法解析處理,則該訂閱類無效,需要忽略
if (!(typeMirror instanceof DeclaredType) ||
!(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
skipReason = "event type cannot be processed";
}
if (skipReason == null) {
TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
// 3.2 如果方法參數對應的類及其父類不是public的 或 其包名和eventBusIndex的包名不一樣,則該訂閱類無效,需要忽略
if (!isVisible(myPackage, eventTypeElement)) {
skipReason = "event type is not public";
}
}
if (skipReason != null) {
boolean added = classesToSkip.add(skipCandidate);
// ...
break;
}
}
}
// 4.獲取父類,接著遍歷父類的訂閱方法
subscriberClass = getSuperclass(subscriberClass);
}
}
}
// 判斷typeElement對應的類對于索引類是否可見
private boolean isVisible(String myPackage, TypeElement typeElement) {
// 獲取類的修飾符
Set<Modifier> modifiers = typeElement.getModifiers();
boolean visible;
if (modifiers.contains(Modifier.PUBLIC)) {
// 如果類是public的,則對于索引類是可見的
visible = true;
} else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
// 如果類是private或protected的,則對于索引類是不可見的
visible = false;
} else {
// 如果類的包名和索引類的包名一樣,即在同一個包目錄下,則對于索引類是可見的
String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
if (myPackage == null) {
visible = subscriberPackage.length() == 0;
} else {
visible = myPackage.equals(subscriberPackage);
}
}
return visible;
}
// 獲取父類元素
private TypeElement getSuperclass(TypeElement type) {
// TypeElement.getSuperclass() 返回此類型元素的直接超類。如果此類型元素表示一個接口或者類java.lang.Object,則返回一個種類為 NONE 的 NoType
// TypeKind.DECLARED表示一個類或接口類型
// 如果type的直接超類是一個類或接口,且不是java.或javax.或android.包下的系統類,則返回它的直接超類
if (type.getSuperclass().getKind() == TypeKind.DECLARED) {
TypeElement superclass = (TypeElement) processingEnv.getTypeUtils().asElement(type.getSuperclass());
String name = superclass.getQualifiedName().toString();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
return null;
} else {
return superclass;
}
} else {
return null;
}
}
// 創建索引文件并輸出內容
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”);
// 將訂閱方法輸出到索引文件
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
}
}
}
}
// 將訂閱方法輸出到索引文件
private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
// 1.遍歷訂閱類
for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
// 2.如果是無效訂閱類(例如訂閱類或訂閱方法參數對應的類對于索引文件不可見),則跳過該類的訂閱方法
if (classesToSkip.contains(subscriberTypeElement)) {
continue;
}
// 2.獲取訂閱類的全限定名(即 包名.類名 )
String subscriberClass = getClassString(subscriberTypeElement, myPackage);
// 3.如果訂閱類對應索引類可見,則寫入內容到索引文件
if (isVisible(myPackage, subscriberTypeElement)) {
writeLine(writer, 2,
"putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
"true,", "new SubscriberMethodInfo[] {");
List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
// 4.寫入SubscriberMethodInfo到索引類中,省略該部分內容,主要就是寫入訂閱方法名,參數類型,ThreadMode,priority、sticky。
writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
writer.write(" }));\n\n");
} else {
writer.write(" // Subscriber not visible to index: " + subscriberClass + "\n");
}
}
}
}
總結:
- eventBus注解處理器只收集被@Subscribe修飾的、public的、非static的、只有一個參數的方法
- 如果訂閱類及其父類對于索引類不可見,則忽略該訂閱類
- 如果訂閱方法的參數類型不是一個類或接口類型,則無法解析處理,忽略該訂閱類
- 如果訂閱方法的參數對應的類及其父類對于索引類不可見,則忽略該訂閱類
ps:
關于編譯時注解的一些參考資料: