同事写了RxBus框架,但不支持注解形式,刚好大家都有需求,就加入了注解,在编译时期生成固定的注册与解注册代码。
常规用法
先声明一个需要监听事件的Listener
public RxBus.OnEventListener textEventListener = new RxBus.OnEventListener() {
@Override
public void onEvent(Object o) {
ExpEvent expEvent = (ExpEvent) o;
RevFragment.this.postTV.setText(postTV.getText()+","+expEvent.value);
}
};
然后在初始化页面时注册
RxBus.getDefault().register(textEventListener, ExpEvent.class);
接着在页面销毁时解注册
RxBus.getDefault().unregister(textEventListener);
使用注册用法
声明一个监听的Event方法,参数为接收的参数
@InjectMethodBind
public void textEvent(ExpE e) {
postMethodTV.setText(postMethodTV.getText() + "," + (e.value));
}
在页面初始话时注册
RxBus.getDefault().inject(this);
页面销毁时解注册
RxBus.getDefault().unInject(this);
编译时期自动生成的代码
public class RevFragment_BindInject implements Inject<RevFragment> {
public RxBus.OnEventListener textEvent_bind;
@Override
public void inject(final RevFragment host) {
textEvent_bind = new RxBus.OnEventListener() {
@Override
public void onEvent(final Object object) {
ExpE o = (ExpE)object;
host.textEvent(o);
}
};
RxBus.getDefault().register(textEvent_bind,0,ExpE.class);
}
@Override
public void unInject(final RevFragment host) {
RxBus.getDefault().unregister(textEvent_bind);
}
}
RxBus注解原理
注解(Annotation)
也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释.
元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
- @Target(用于描述注解的使用范围)
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
- @Retention(表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效))
- SOURCE:在源文件中有效(即源文件保留 源码时)
- CLASS:在class文件中有效(即class保留 编译时)
- RUNTIME:在运行时有效(即运行时保留 运行时)
- @Documented(描述其它类型的annotation应该被作为被标注的程序成员的公共API)
- Documented是一个标记注解,没有成员。
- @Inherited (是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。)
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值
/**
* Created by huilin on 2017/7/14.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface InjectMethodBind {
int type() default 0;
}
因为我们使用范围是在方法上,所以Target使用ElementType.METHOD,编译时期用到,所以Retention使用RetentionPolicy.CLASS。注解在方法上,参数名字就是方法名字type,参数类型就是方法返回类型int。
运行时读取注解方法
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
Method[] fucs = clazz.getDeclaredMethods();
for(Method fuc:fucs){
if(fuc.isAnnotationPresent(InjectMethodBind.class)){
InjectMethodBind bindView = (InjectMethodBind) fuc.getAnnotation(InjectMethodBind.class);
}
}
但是由于反射造成一定运行效率的损耗,所以我们会更青睐于编译时注解的框架,编译时生成代码。用到了APT(Annotation Processing Tool ),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。
编写一个编译时解析注解框架
•
rxbus-inject --API android模块
• rxbus-annotation --注解相关 java模块
• rxbus-compile– 注解处理器 java模块
• rxbus-annotation --注解相关 java模块
• rxbus-compile– 注解处理器 java模块
第一步自定义注解,刚刚上边已经写了我们要用的自定义注解
第二步创建使用的API
public interface Inject<T> {
void inject(T host);
void unInject(T host);
}
public interface Inject<T> {
void inject(T host);
void unInject(T host);
}
我们需要一个绑定的入口inject与一个解绑的入口unInject,我们编译生成的代码将会是下面这样,两个方法参数都是当前需要接收页面所在的类,可以activity fragment 普通类...
public class RevFragment_BindInject implements Inject<RevFragment> {
public RxBus.OnEventListener textEvent_bind;
@Override
public void inject(final RevFragment host) {
textEvent_bind = new RxBus.OnEventListener() {
@Override
public void onEvent(final Object object) {
ExpE o = (ExpE)object;
host.textEvent(o);
}
};
RxBus.getDefault().register(textEvent_bind,0,ExpE.class);
}
@Override
public void unInject(final RevFragment host) {
RxBus.getDefault().unregister(textEvent_bind);
}
}
根据上面代码,就可以总结出inject()方法怎么写
public class EventInject {
private static final HashMap<String, Inject> injectHashMap = new HashMap<>();
public static void inject(Object host) {
String className = host.getClass().getName();
Inject inject = injectHashMap.get(className);
if (inject == null) {
try {
Class<?> aClass = Class.forName(className + "_BindInject");
inject = (Inject) aClass.newInstance();
inject.inject(host);
injectHashMap.put(className, inject);
} catch (Exception e) {
e.printStackTrace();
}
}else{
injectHashMap.get(className).inject(host);
}
}
public static void unInject(Object host) {
String className = host.getClass().getName();
Inject inject = injectHashMap.get(className);
if (inject != null) {
inject.unInject(host);
}
}
}
首先我们需要一个接口 Inject,然后为每一个注解类都生成一个对应的_BindInject类并且实现这个接口,然后实现具体的注入逻辑。在 inject() 方法中首先找到调用者对应的 Inject实现类,然后调用其内部的具体逻辑来达到注入的目的。
另外代码中使用到了一点反射,所以为了提高效率,避免每次注入的时候都去找 Inject对象,这里用一个 Map 将第一次找到的对象缓存起来,后面用的时候直接从 Map 里面取。到此,API 模块的设计基本搞定了,接下来就是去通过注解处理器为每一个注解类生成 Inject的实现类。
创建注解处理器
创建rxbus-compile – 注解处理器 java模块
添加所需要的依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':rxbus-annotation')//前面我们定义的注解
compile 'com.squareup:javapoet:1.7.0'//是square公司的开源库。正如其名,java诗人,生成java源文件
compile 'com.google.auto.service:auto-service:1.0-rc2'//是 Google的库,主要用于注解 Processor,对其生成 META-INF 配置信息。
}
编写模板代码注解处理器
@AutoService(Processor.class)
public class BindEventProcesser extends AbstractProcessor {
/**
* 文件辅助类
*/
private Filer filer;
/**
* 元素辅助类
*/
private Elements elements;
/**
* 存储注解类的映射
*/
private HashMap<String, AnnotatedClass> annotatedClassHashMap;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
elements = processingEnv.getElementUtils();
annotatedClassHashMap = new HashMap<>();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
annotations.clear();
processEventMethod(roundEnv);
for (AnnotatedClass annotatedClass : annotatedClassHashMap.values()){
try {
annotatedClass.generateFile().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 解析注解
* @param roundEnv
*/
private void processEventMethod(RoundEnvironment roundEnv) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectMethodBind.class);
for (Element element: elementsAnnotatedWith){
AnnotatedClass annotatedClass = getAnnotatedClass(element);
annotatedClass.addBindMethod(new BindEventMethod(element));
}
}
/**
* 指定使用的java版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
*
*指定要被注解处理器处理的注解
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<>();
set.add(InjectMethodBind.class.getCanonicalName());
return set;
}
private AnnotatedClass getAnnotatedClass(Element element) {
TypeElement className = (TypeElement) element.getEnclosingElement();
String fullName = className.getQualifiedName().toString();
AnnotatedClass annotatedClass = annotatedClassHashMap.get(fullName);
if (annotatedClass == null) {
annotatedClass = new AnnotatedClass(className, elements);
annotatedClassHashMap.put(fullName, annotatedClass);
}
return annotatedClass;
}
}
具体变量含义已注释在代码中
用 @AutoService 来注解这个处理器,可以自动生成配置信息。
在 init() 可以初始化拿到一些实用的工具类在 getSupportedAnnotationTypes() 方法中返回所要处理的注解的集合。getSupportedSourceVersion() 方法中返回 Java 版本。这几个方法写法基本上都是固定的,重头戏在 process() 方法,可以获取到要处理的所有注解集合,遍历即可。
Element元素
Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的Element。它可以代表包、类、接口、方法、字段等多种元素种类,具体看getKind()方法中所指代的种类
• PackageElement 表示一个包程序元素
• TypeElement 表示一个类或接口程序元素
• VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
• ExecutableElement 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
• TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数
• PackageElement 表示一个包程序元素
• TypeElement 表示一个类或接口程序元素
• VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
• ExecutableElement 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
• TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数
public class Sample // TypeElement
{ // TypeParameterElement
private int num; // VariableElement
String name; // VariableElement
public Sample() {} // ExecuteableElement
public void setName( // ExecuteableElement
String name // VariableElement
) {}
}
这些 Element 元素,相当于 XML 中的 DOM 树,可以通过一个元素去访问它的父元素或者子元素。element.getEnclosingElement();// 获取父元素 element.getEnclosedElements();// 获取子元素
BindEventMethod类
public class BindEventMethod {
private ExecutableElement executableElement;
private int type;
public BindEventMethod(Element element) {
if (element.getKind() != ElementKind.METHOD) {
throw new IllegalArgumentException("not method");
}
executableElement = (ExecutableElement) element;
if(getParameters().size() != 1){
throw new IllegalArgumentException("parameter not equal to 1");
}
InjectMethodBind methodBind = executableElement.getAnnotation(InjectMethodBind.class);
type = methodBind.type();
}
public int getType() {
return type;
}
public Name getMethodName() {
return executableElement.getSimpleName();//获取注解方法的方法名
}
public List<? extends VariableElement> getParameters(){
return executableElement.getParameters();//获取注解方法上的参数
}
}
AnnotatedClass类
AnnotatedClass 表示一个注解类,里面声明了 private ArrayList<BindEventMethod> bindEventMethods;存放注解类所生有被注解的方法
public JavaFile generateFile() {
//生成类名
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(typeElement.getSimpleName() + "_BindInject")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(Common.RXBUS_INJECT_PACK_NAME, "Inject"), TypeName.get(typeElement.asType())));
//声明inject方法
MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(typeElement.asType()), "host", Modifier.FINAL);
//声明unInject方法
MethodSpec.Builder unInjectBuilder = MethodSpec.methodBuilder("unInject")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(typeElement.asType()), "host", Modifier.FINAL);
// FieldSpec.Builder host = FieldSpec.builder(TypeName.get(typeElement.asType()),
// "m"+typeElement.getSimpleName().toString(), Modifier.PUBLIC);
// classBuilder.addField(host.build());
// injectBuilder.addStatement("m$N = host",typeElement.getSimpleName().toString());
//解析注解方法
for (BindEventMethod bindEventMethod : bindEventMethods) {
//声明匿名内部类OnEventListener
TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(ClassName.get(Common.RXBUS_PACK_NAME, "RxBus", "OnEventListener"))
.addMethod(MethodSpec.methodBuilder("onEvent")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(TypeName.OBJECT, "object", Modifier.FINAL)
.addStatement("$T o = ($T)object", TypeName.get(bindEventMethod.getParameters().get(0).asType()),
TypeName.get(bindEventMethod.getParameters().get(0).asType()))
.addStatement("host.$N($L)", /**typeElement.getSimpleName().toString(),**/ bindEventMethod.getMethodName(), "o").build()
// .addStatement("m$N.$N($L)", typeElement.getSimpleName().toString(), bindEventMethod.getMethodName(), "o").build()
).build();
//声明类成员变量(匿名内部类)
FieldSpec.Builder fieldBuilder = FieldSpec.builder(ClassName.get(Common.RXBUS_PACK_NAME, "RxBus", "OnEventListener"),
bindEventMethod.getMethodName().toString() + "_bind", Modifier.PUBLIC);
classBuilder.addField(fieldBuilder.build());
//inject方法添加代码(成员变量赋值)
injectBuilder.addStatement(bindEventMethod.getMethodName().toString() + "_bind = $L", typeSpec);
//inject方法添加代码(RxBus注册)
injectBuilder.addStatement(" RxBus.getDefault().register($L,$L,$T.class)", bindEventMethod.getMethodName().toString() + "_bind",
bindEventMethod.getType(), TypeName.get(bindEventMethod.getParameters().get(0).asType()));
//UnInject方法添加(RxBus解绑)
unInjectBuilder.addStatement("RxBus.getDefault().unregister($L)", bindEventMethod.getMethodName().toString() + "_bind");
}
//类增加inject方法
classBuilder.addMethod(injectBuilder.build());
//类增加unInject方法
classBuilder.addMethod(unInjectBuilder.build());
return JavaFile.builder(getPackName(), classBuilder.build()).build();
}
在generateFile() 方法中,按照前边设计的要生成的代码模板,利用 JavaPoet 的 API 生成代码。这部分没啥特别的姿势,照着
JavaPoet 文档 来就好了,文档写得很细致。