在上一篇EventBus3.0源码解析中,在介绍查找订阅方法时提到了APT
解析,当时一笔带过,主要是觉得这个特性比较重要,所以单独拎出来写一篇来介绍。
源码
先来回忆下查找订阅方法:
List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
// 使用反射查找
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 使用生成的index查找
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;
}
}
复制代码
在findSubscriberMethods
方法中,会根据ignoreGeneratedIndex
来进行策略执行,这个变量默认是false
,调用了findUsingInfo
方法:
private List findUsingInfo(Class> subscriberClass) {
// 准备一个findState实例
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);
}
复制代码
方法如其名,这个方法主要就是用来查找本地已经生成的订阅者信息,如果没有查找到还是要去使用反射获取。关键方法就是getSubscriberInfo
方法,它返回了一个SubscriberInfo实例
并保存在了FindState实例
中,来看看SubscriberInfo
里有什么:
public interface SubscriberInfo {
// 订阅类
Class> getSubscriberClass();
// 订阅方法集合
SubscriberMethod[] getSubscriberMethods();
SubscriberInfo getSuperSubscriberInfo();
boolean shouldCheckSuperclass();
}
复制代码
SubscriberInfo
是一个接口,通过这个接口可以拿到封装了订阅类、订阅方法以及父类的SubscriberInfo
。再回到上一步,看看关键方法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;
}
}
// 这里判断了subscriberInfoIndexes不为空,从subscriberInfoIndexes中去取SubscriberInfo
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
复制代码
一共两步,第一步是在查找过一次之后并且找到了目标SubscriberInfo
之后才会执行。重点是在第二步,判断了一个参数subscriberInfoIndexes
不为空,然后从subscriberInfoIndexes
中遍历出目标类的SubscriberInfo
。那么subscriberInfoIndexes
又是什么呢:
class SubscriberMethodFinder {
// 一个SubscriberInfoIndex集合
private List subscriberInfoIndexes;
...
}
// 一个根据订阅类查找订阅信息的接口
public interface SubscriberInfoIndex {
SubscriberInfo getSubscriberInfo(Class> subscriberClass);
}
复制代码
subscriberInfoIndexes
是一个SubscriberInfoIndex
集合,它是在EventBus初始化
时被赋值的:
public class EventBus {
EventBus(EventBusBuilder builder) {
...
// 传入builder.subscriberInfoIndexes
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
...
}
}
复制代码
而EventBus初始化
则是使用的Builder模式
,Builder里有个addIndex
方法:
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if(subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
复制代码
到了这里显而易见,SubscriberInfoIndex
是由我们传参进Builder
的。其实SubscriberInfoIndex
就是APT
预先解析出来的订阅者信息提供者,它需要开发者自行在编译器中配置APT
后编译生成,接下来就来看看如何才能生成并使用索引类SubscriberInfoIndex
。
Subscriber Index
APT(Annotation Processing Tool)配置
首先要在项目module的build.gradle
中引入EventBusAnnotationProcessor依赖
, 并设置生成类参数
android {
defaultConfig {
javaCompileOptions {
// 注解处理器参数配置
annotationProcessorOptions {
// 配置参数名和值
arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
}
}
}
}
dependencies {
// 注解依赖
implementation 'org.greenrobot:eventbus:3.0.0'
// 注解处理器依赖
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}
复制代码
如果使用的是Kotlin
语言,就需要使用Kotlin
的专用APT:kapt
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
dependencies {
implementation 'org.greenrobot:eventbus:3.0.0'
kapt 'org.greenrobot:eventbus-annotation-processor:3.0.0'
}
kapt {
arguments {
// 包名可以自定义
arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
}
}
复制代码
如果项目中是Java
和Kotlin
同时使用,可能会存在annotationProcessor
和kapt
同时存在,建议统一改为kapt
,因为后者会兼容前者。
Index应用
在上一步完成后,只需要Rebuild Project
,就会在项目路径app-build-generated-source-apt/kapt
下看到生成的索引类:MyEventBusIndex.java
,它实现了SubscriberInfoIndex
接口,实现了getSubscriberInfo
方法。从上边的源码分析就知道,接下来只需要将生成的索引类传参进EventBus
中:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
复制代码
由源码可知,EventBus
中保存的是一个Subscriber Index集合
,所以addIndex
方法可以调用多次,这样,在非application
的Model
中也可以生成各自的Index类,最后统一添加到EventBus
中。
来看看生成的索引类MyEventBusIndex.java
:
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;
// 类初始化时直接赋值
static {
SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
// 直接new一个SimpleSubscriberInfo并加入SUBSCRIBER_INDEX
putIndex(new SimpleSubscriberInfo(com.example.myapp.MainActivity.Companion.class,
true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("handleEvent", com.example.myapp.MyEvent.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;
}
}
}
复制代码
MyEventBusIndex
在初始化时,直接会将订阅者相关信息缓存在Map中,并实现了SubscriberInfoIndex接口
,在实现方法getSubscriberInfo
中根据订阅类返回对应的订阅者信息。到了这里,你肯定会疑惑,EventBus
是如何在编译期间就找到了所有订阅信息并且是如何生成了一个Java
文件的,奥秘就在APT
技术中。
EventBusAnnotationProcessor
在上文中,整个MyEventBusIndex
文件都是由APT
在编译时解析注解生成的。显然,EventBus
自定义了自己的注解处理器。
我们知道,自定义注解处理器只要三步:
- 新建自定义注解器(其实就是一个java类),继承自
Java
提供的AbstractProcessor
- 实现
process
方法,开发者就是在这个方法中进行注解解析并生成Java类 - 注册到
Javac
(编译器)
这样,在编译期间,Javac
会扫描注解,检查AbstractProcessor
的子类,并且调用该子类的process
函数,然后将添加了注解的所有元素都传递到process
函数中,执行我们的处理逻辑,生成我们想要的Java文件。
那么重点就在EventBus
的EventBusAnnotationProcessor的process
方法。在开始之前,需要了解下Element
类:
Element
要想自定义APT
,必须掌握Element,它是APT
技术的基础。Element
只有在编译期间是可见的,因为它是用来在编译期间描述Java文件的静态结构的一种类型,Element
可以表示包、类或方法。JDK
提供了5种Element
,它们都继承自Element
:
- javax.lang.model.element.Element
- javax.lang.model.element.ExecutableElement
- javax.lang.model.element.PackageElement
- javax.lang.model.element.TypeElement
- javax.lang.model.element.TypeParameterElement
- javax.lang.model.element.VariableElement
复制代码
它们分别表示:
PackageElement
:表示一个包程序元素。提供对有关包及其成员的信息的访问。TypeElement
:表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注释类型是一种接口。ExecutableElement
:表示某个类或接口的方法、构造方法或初始化程序,包括注释类型元素。TypeParameterElement
:表示一般类、接口、方法或构造方法元素的形式类型(泛型)参数。VariableElement
:表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
举个栗子:
package com.example; // PackageElement
public class Demo<T extends List> { // Demo类是TypeElement,T是TypeParameterElement
private int a; // VariableElement
private String b; // VariableElement
public Demo () {} // ExecuteableElement
public void setValue (int value) {} // 方法setValue是ExecuteableElement,参数value是VariableElement
}
复制代码
再介绍下几个涉及到的Element
方法:
asType
:返回此元素定义的类型。返回值用TypeMirror
表示,TypeMirror
表示了Java编程语言中的类型,包括基本类型,一般用来做类型判断。
getModifiers
:返回此元素的修饰符,不包括注释。但包括显式修饰符,比如接口成员的 public
和 static
修饰符。
getEnclosingElement
:返回封装此元素的最里层元素。
- 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素。
- 如果此元素是顶层类型,则返回它的包。
- 如果此元素是一个包,则返回
null
。 - 如果此元素是一个类型参数,则返回
null
。
process
接下来进入正题,看看EventBus
注解处理器的process
方法:
public class EventBusAnnotationProcessor extends AbstractProcessor {
public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex";
/** Found subscriber methods for a class (without superclasses). */
// 自定义的数据结构,保存`>`类型数据;这里的key是订阅类
private final ListMap methodsByClass = new ListMap<>();
// 保存不合法的元素,这里的key是订阅类
private final Set classesToSkip = new HashSet<>();
...
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment env) {
// messager用于输出log
Messager messager = processingEnv.getMessager();
try {
// 获取到我们在gradle中为apt设置的参数值,即生成类的完整路径,通过它可以获得包名和类名
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
if (index == null) {
return false;
}
...
// 取出所有订阅者信息
collectSubscribers(annotations, env, messager);
// 检查订阅者信息
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {
// 查找到的注解的方法不为空,生成Java文件
createInfoIndexFile(index);
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
writerRoundDone = true;
} catch (RuntimeException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.ERROR, e);
}
return true;
}
}
复制代码
process
方法中做了三件事:
- 查找到所有订阅者
- 检查出不合法的订阅者
- 根据前两步的结果生成
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) {
// 元素必须是方法,因为@Subscribe只能注解方法
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
// 检查方法,条件:订阅方法必须是非静态的,公开的,参数只能有一个
if (checkHasNoErrors(method, messager)) {
// 取封装订阅方法的类
TypeElement classElement = (TypeElement) method.getEnclosingElement();
// 以类名为key,保存订阅方法
methodsByClass.putElement(classElement, method);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
}
}
}
}
复制代码
这里的流程也是很简单的,就是遍历被注解标记的所有元素,筛选出合法的订阅方法,具体看注释即可。checkHasNoErrors
方法比较简单,就不贴代码了。 这里还有个陌生的方法:
RoundEnvironment
的getElementsAnnotatedWith
方法, 这个方法会返回被当前注解标记的所有元素,可能是类、变量、方法等。
在方法最后,将筛选出来的订阅者和方法都保存在了methodsByClass
这个容器里,这个容器的数据结构是
,可以存储订阅类中的多个方法。
checkForSubscribersToSkip
查找不合法的订阅者
private void checkForSubscribersToSkip(Messager messager, String myPackage) {
// 遍历所有订阅类
for (TypeElement skipCandidate : methodsByClass.keySet()) {
TypeElement subscriberClass = skipCandidate;
//开启子类到父类的while循环检查
while (subscriberClass != null) {
// 订阅类必须是可访问的,否则记录并直接break,检查下一个订阅类
if (!isVisible(myPackage, subscriberClass)) {
boolean added = classesToSkip.add(skipCandidate);
...
break;
}
// 拿到订阅类的所有订阅方法
List methods = methodsByClass.get(subscriberClass);
if (methods != null) {
// 检查所有订阅方法
for (ExecutableElement method : methods) {
String skipReason = null;
// 拿到订阅方法的参数,开始对参数进行检查
VariableElement param = method.getParameters().get(0);
// 取得参数(Event)的类型
TypeMirror typeMirror = getParamTypeMirror(param, messager);
// 参数(Event)的类型必须是类或接口
if (!(typeMirror instanceof DeclaredType) ||
!(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
skipReason = "event type cannot be processed";
}
if (skipReason == null) {
// 拿到参数(Event)元素
TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
// 检查参数(Event)元素是不是可访问的
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;
}
}
}
// 切换到父类,若父类是系统的类,则返回null,结束while循环
subscriberClass = getSuperclass(subscriberClass);
}
}
}
复制代码
可以看到这个方法主要是对第一步查找到的订阅者和订阅方法进行检查,因为要生成的索引类中要访问订阅类和事件,所以必须要求订阅类和Event参数是可访问的,并且Event参数必须是类或接口类型,因为EventBus
的订阅方法是不支持基本类型的。
来看下可访问性检查isVisible
:
private boolean isVisible(String myPackage, TypeElement typeElement) {
// 获取修饰符
Set modifiers = typeElement.getModifiers();
boolean visible;
if (modifiers.contains(Modifier.PUBLIC)) {
// public的直接return true
visible = true;
} else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
// private和protected的直接return false
visible = false;
} else {
// 获取元素所在包名
String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
if (myPackage == null) {
// 是否都在最外层,没有包名
visible = subscriberPackage.length() == 0;
} else {
// 是否在同一包下
visible = myPackage.equals(subscriberPackage);
}
}
return visible;
}
复制代码
获取元素类型方法getParamTypeMirror
:
private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) {
// 获取元素类型
TypeMirror typeMirror = param.asType();
// 元素类型必须得是类或接口类型
if (typeMirror instanceof TypeVariable) {
// 获取该类型变量的上边界,如果有extends,则返回父类,否则返回Object
TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound();
// 是声明类型
if (upperBound instanceof DeclaredType) {
if (messager != null) {
messager.printMessage(Diagnostic.Kind.NOTE, "Using upper bound type " + upperBound +
" for generic parameter", param);
}
// 替换参数类型为上边界的类型
typeMirror = upperBound;
}
}
return typeMirror;
}
复制代码
createInfoIndexFile
生成Java
文件:
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
// 通过编译环境的文件工具创建Java文件
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);
//开始向Java文件中写入代码
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, SubscriberInfo> SUBSCRIBER_INDEX;\n\n" );
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap, 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
}
}
}
}
复制代码
惊呆了有没有,果然是大神,没有使用第三方框架比如javapoet
,硬是一行行写出来的,佩服!
重点就在writeIndexLines
方法了:
private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
// 遍历methodsByClass
for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
// 跳过不合格的订阅者
if (classesToSkip.contains(subscriberTypeElement)) {
continue;
}
// 获取订阅类的字符串名称,格式是:包名.类名.class
String subscriberClass = getClassString(subscriberTypeElement, myPackage);
// 再一次检查是否可访问
if (isVisible(myPackage, subscriberTypeElement)) {
writeLine(writer, 2,
"putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
"true,", "new SubscriberMethodInfo[] {");
// 取出订阅类的所有订阅方法
List methods = methodsByClass.get(subscriberTypeElement);
// 生成 [new 一个SubscriberMethodInfo,并将订阅方法的相关信息写入]的代码
writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
writer.write(" }));\n\n");
} else {
writer.write(" // Subscriber not visible to index: " + subscriberClass + "\n");
}
}
}
复制代码
很简单,遍历查找到的所有订阅者,然后跳过不合法的订阅者,生成写入订阅者的代码。显然,writeCreateSubscriberMethods
方法中生成了写入订阅方法的代码:
private void writeCreateSubscriberMethods(BufferedWriter writer, List methods,
String callPrefix, String myPackage) throws IOException {
// 遍历订阅类中的所有订阅方法
for (ExecutableElement method : methods) {
// 获取方法参数
List extends VariableElement> parameters = method.getParameters();
// 取出第一个参数的参数类型
TypeMirror paramType = getParamTypeMirror(parameters.get(0), null);
// 通过参数类型拿到参数元素
TypeElement paramElement = (TypeElement) processingEnv.getTypeUtils().asElement(paramType);
// 方法名
String methodName = method.getSimpleName().toString();
// 参数类型 类名
String eventClass = getClassString(paramElement, myPackage) + ".class";
// 获取方法上的注解
Subscribe subscribe = method.getAnnotation(Subscribe.class);
List parts = new ArrayList<>();
parts.add(callPrefix + "(\"" + methodName + "\",");
String lineEnd = "),";
// 获取注解的值
if (subscribe.priority() == 0 && !subscribe.sticky()) {
if (subscribe.threadMode() == ThreadMode.POSTING) {
parts.add(eventClass + lineEnd);
} else {
parts.add(eventClass + ",");
parts.add("ThreadMode." + subscribe.threadMode().name() + lineEnd);
}
} else {
parts.add(eventClass + ",");
parts.add("ThreadMode." + subscribe.threadMode().name() + ",");
parts.add(subscribe.priority() + ",");
parts.add(subscribe.sticky() + lineEnd);
}
// 生成代码
writeLine(writer, 3, parts.toArray(new String[parts.size()]));
if (verbose) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Indexed @Subscribe at " +
method.getEnclosingElement().getSimpleName() + "." + methodName +
"(" + paramElement.getSimpleName() + ")");
}
}
}
复制代码
writeLine
方法只是按Java
格式生成代码,无需深入了解。最后生成的代码就是上面提到的MyEventBusIndex.java
。
总结
注解处理器的作用其实就是提前在编译阶段就把订阅者的信息解析出来,在运行期间直接使用,极大的提高了效率和性能!ButterKnife
等知名框架都用到了这种技术,掌握它还是很有必要的!