1.配置信息
1.1 创建一个java-library 用于存放 注解的类新型
[:annotion]
apply plugin: 'java-library'
然后创建一个用于绑定View的注解,指定为编译时注解类型,用于Filed的注入类型
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface PBindView {
int value();
}
1.2 创建一个java-library 用于存放编译的实现接口,编译会实现此接口的方法,可以统一管理,方便后面的反射创建
[:arouterapi]
apply plugin: 'java-library'
创建一个管理的接口类
public interface IPBindPath {
Map<String, Integer> getBingGroups(Class mClass);
}
通过传入的对象类型,获取注解声明的所有属性名称和相应的R.id
后面会通过javapoet封装一个Map<Class,Map<String,Integer>> map ,用于存放所有标记了注解的类 以及 该类所有标记的属性值;这里是定义一个方法,方便根据类获取属性并反射注入属性
1.3 创建一个java-library 用于操作自动生成的代码
[:compiler]
apply plugin: 'java-library'
dependencies {
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
implementation "com.squareup:javapoet:1.9.0"
implementation project(":annotion")
implementation project(":arountapi")
}
1.4 在应用内引入
implementation project(":annotion")
annotationProcessor project(":compiler")
implementation project(":common")
2.自动生成代码书写
javapoet书写规范是逆序书写,就是格式按照我们平时创建类的反向思维
我们平时建一个类,是先选一个包名,然后类名,方法名,参数名这种方式
javapoet则是反过来,一层层包装后最终生成
同时注意常用的几种格式参数 $T 指class类型,$N引用类型 ,$S 指字符串类型 $L 指枚举类型或其他int等基本类型
2.1 配置
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.test.annotion.PBindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
...
}
这里格式比较固定
@AutoService 绑定Processor用于工作的类,
@SupportedAnnotationTypes 监听PBindView注解类,或者说所有有PBindView注解的属性都会在这里集中回调
@SupportedSourceVersion 指定支持的版本,这里选的是版本7
private Elements elementUtil;
private Types typeUtil;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtil = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
typeUtil = processingEnvironment.getTypeUtils();
}
这里有四个属性,列一下常用的功能介绍
elementUtil 对元素操作的工具,可以获取对应元素的属性
//获取元素的包名
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString()
//获取activity对应的类型
TypeMirror activityType = elementUtil.getTypeElement("android.app.Activity").asType()
//配合typeUtil进行类型判断,如判断选定的element是否是Activity的子类
typeUtil.isSubtype(element.asType(), activityType)
typeUtil 类型判断
filer 最终执行自动生成的工具,最终根据配置的代码生成相应的类,这个后面会用到
JavaFile.builder(ProcessorConfig.getDefaultPackage(), typeSpec)
.build().writeTo(filer);
messager 日志输出类
messager.printMessage(Diagnostic.Kind.ERROR, "1111");
2.2 编写
核心方法都在process这里处理
2.2.1 列出所有标记了PBindView的元
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set == null || set.size() == 0) return false;
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(PBindView.class);
if (elements == null || elements.size() == 0) {
throw new RuntimeException("未找到匹配的注解");
}
......
}
2.2.2 创建一个静态的成员变量map
private static Map<Class, Map<String, Integer>> map = new HashMap();
TypeName pathName = ParameterizedTypeName.get(ClassName.get(Map.class), TypeName.get(String.class), TypeName.get(Integer.class));
TypeName groupName = ParameterizedTypeName.get(ClassName.get(Map.class), TypeName.get(Class.class), pathName);
FieldSpec fieldSpec = FieldSpec.builder(groupName, "map")
.addModifiers(Modifier.STATIC)
.addModifiers(Modifier.PRIVATE)
.initializer("new $T()", HashMap.class)
.build();
泛型创建也是逆向思维,先创建一个Map<String,Integer>的类型,然后再在这个基础上创建一个Map<Class,Map>的类型
2.2.3 创建一个构造方法,里面调用另一init()方法
MethodSpec conMethodSpc = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("init()")
.build();
2.2.4 创建最上面接口的实现方法
@Override
public Map<String, Integer> getBingGroups(Class className) {
return map.get(className);
}
MethodSpec methodSpec = MethodSpec.methodBuilder("getBingGroups")
.addParameter(ParameterSpec.builder(TypeName.get(Class.class), "className").build())
.addAnnotation(Override.class)
.returns(pathName)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $N.get($N)", "map", "className")
.build();
2.2.5 定义一个往map里添加数据的方法
private void addToPath(Class className, String fieldName, Integer id) {
Map<String, Integer> groupPath = map.get(className);
if (groupPath == null) {
groupPath = new HashMap();
map.put(className, groupPath);
}
groupPath.put(fieldName, id);
}
MethodSpec methodAdd = MethodSpec.methodBuilder("addToPath")
.addParameter(ParameterSpec.builder(TypeName.get(Class.class), "className").build())
.addParameter(ParameterSpec.builder(TypeName.get(String.class), "fieldName").build())
.addParameter(ParameterSpec.builder(TypeName.get(Integer.class), "id").build())
.addModifiers(Modifier.PRIVATE)
.addStatement("$T $N = $N.get($N)", pathName, "groupPath", "map", "className")
.addStatement("if($N == null){", "groupPath")
.addStatement("$N = new $T()", "groupPath", HashMap.class)
.addStatement("$N.put($N,$N)", "map", "className", "groupPath")
.addStatement("}")
.addStatement("$N.put($N,$N)", "groupPath", "fieldName", "id").build();
变量类型引用$N ,如果传入的字符串比如 groupPath,局部变量和成员变量都没声明,则会创建一个这个名字的变量,否则直接引用前面创建的变量
类引用 $T 引用的是类名称,不是完整的类文件;比如上面虽然写了$T() -> HashMap.class ,但只会把HashMap这个字符串传到这个表达式里替换,如果要引用 .class类型则要自己补齐,比如$T.class这样
值得一提的是,内部类或者类部枚举等因为不能直接访问,表达式可以写成 $T.$T 和 $T.$L 前面是外部类,后面是内部属性,等价于 A.B 即A类的内部类B的表现形式
补上上面定义的init方法,在构造方法里调用
public AROUTR_TOTEL_BIND_VIEW_CLASS() {
init();
}
MethodSpec.Builder initBuilder = MethodSpec.methodBuilder("init")
.returns(TypeName.VOID)
.addModifiers(Modifier.PRIVATE);
2.2.6 遍历有标记的注解参数,依次写入
TypeMirror activityType = elementUtil.getTypeElement(ProcessorConfig.ACTIVITY_TYPE).asType();
for (Element element : elements) {
PBindView pBindView = element.getAnnotation(PBindView.class);
int id = pBindView.value();
//获取父节点
Element parentElement = element.getEnclosingElement();
if (typeUtil.isSubtype(parentElement.asType(), activityType)) {
initBuilder.addStatement("addToPath($T.class,$S,$L)", parentElement.asType(), element.getSimpleName(), id);
} else {
throw new RuntimeException("暂不支持非ActivityViewBind");
}
}
getEncloseingElement是获取当期注解属性的父节点,这里就对应标记了该注解的Activity
这里偷懒了,最好按Element传入配置,方便后续获取,这里是直接获取属性名称,混淆会出问题
2.2.7 把所有的方法和变量全部装构建到类中
TypeSpec typeSpec = TypeSpec.classBuilder("AROUTR_TOTEL_BIND_VIEW_CLASS")
.addField(fieldSpec)
.addMethod(conMethodSpc)
.addMethod(methodAdd)
.addMethod(initBuilder.build())
.addMethod(methodSpec)
.addSuperinterface(TypeName.get(IPBindPath.class)) //添加实现的接口
.addModifiers(Modifier.PUBLIC)
.build();
2.2.8 执行生成文件的代码
try {
JavaFile.builder("com.test.test.arouter", typeSpec)
.build().writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
然后我在测试的两个Activity分别定义了一个view去引用注解,最终生成的类结构是
package com.test.test.arouter;
import com.arountapi.IPBindPath;
import com.example.testmodule.debug.A;
import com.example.testmodule.debug.TestModuleActivity;
import java.lang.Class;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.util.HashMap;
import java.util.Map;
public class AROUTR_TOTEL_BIND_VIEW_CLASS implements IPBindPath {
private static Map<Class, Map<String, Integer>> map = new HashMap();
public AROUTR_TOTEL_BIND_VIEW_CLASS() {
init();
}
private void addToPath(Class className, String fieldName, Integer id) {
Map<String, Integer> groupPath = map.get(className);
if (groupPath == null) {
groupPath = new HashMap();
map.put(className, groupPath);
}
groupPath.put(fieldName, id);
}
private void init() {
addToPath(A.class, "tvTextView", 2131165362);
addToPath(TestModuleActivity.class, "imageView", 2131165286);
}
@Override
public Map<String, Integer> getBingGroups(Class className) {
return map.get(className);
}
}
分别把属性名称和id都存入了map ,并把类对象作为key再包装成一个方便查询的大map
2.2.9 定义注入入口
public class BindUtils {
public static void bind(Activity activity) {
try {
Class<?> mClass = Class.forName("com.test.test.arouter.AROUTR_TOTEL_BIND_VIEW_CLASS");
IPBindPath ipBindPath = (IPBindPath) mClass.newInstance();
invokeDatas(activity,ipBindPath.getBingGroups(activity.getClass()));
} catch (Exception e) {
e.printStackTrace();
}
}
private static void invokeDatas(Object main, Map<String, Integer> map) {
try {
Method findViewById = main.getClass().getMethod("findViewById", int.class);
for (String key : map.keySet()) {
Field field = main.getClass().getDeclaredField(key);
field.setAccessible(true);
Object res = findViewById.invoke(main, map.get(key));
field.set(main, res);
}
} catch (Exception e) {
e.printStackTrace();
}
}
很简单,只需要反射拿到这个类的实例,然后去查询当期类对应的map集合,反射给属性赋值即可
最后的核心还是依赖反射,很多常见的注入的框架比如 EventBus ,Arouter 等都是这个原理