xiuyuantech 博客: https://xiuyuantech.github.io/

注解处理器是一种处理注解的工具,确切的说它是 javac 的一个工具,它用来在编译时扫描和处理注解。 注解处理器以 Java 代码 (或者编译过的字节码) 作为输入,生成.java 文件作为输出,减少手动的代码输入。 简单来说就是在编译期,通过注解生成.java 文件。比如我们经常用的轮子 Dagger2, ButterKnife, EventBus 都在用,我们要紧跟潮流来看看 APT 技术的来龙去脉。
实现方式 创建 Android Module 命名为 app 创建 Java library Module 命名为 apt-annotation 创建 Java library Module 命名为 apt-processor 依赖 apt-annotation 创建 Android library Module 命名为 apt-library 依赖 apt-annotation、auto-service 可选 compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
注意不可都放在一个 Module 中,放在一个 Module 中不会生效!
功能主要分为三个部分
apt-annotation:存放自定义注解 apt-processor:注解处理器,根据 apt-annotation 中的注解,在编译期生成 xxx.java 代码 apt-library:工具类,实现业务逻辑的绑定。

apt-annotation (自定义注解)
@Retention(RetentionPolicy.CLASS):表示编译时注解 @Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)
@Retention: 定义被保留的时间长短 RetentionPoicy.SOURCE、RetentionPoicy.CLASS、RetentionPoicy.RUNTIME @Target: 定义所修饰的对象范围 TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE 等
apt-processor (注解处理器)
添加依赖
dependencies {
implementation project(':apt-annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) //optional
}
自定义处理器 BindViewProcessor
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Messager messager;
private Trees trees;
private TreeMaker maker;
private Name.Table names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
trees = Trees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
maker = TreeMaker.instance(context);
names = Names.instance(context).table;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
Set<? extends Element> elements = roundEnv.getRootElements();
for (Element each : elements) {
if (each.getKind() == ElementKind.CLASS) {
JCTree tree = (JCTree) trees.getTree(each);
if (tree != null) {
if (tree instanceof JCTree.JCClassDecl) {
if (((JCTree.JCClassDecl) tree).name.toString().equals(
"WebAppInterface")) {
tree.accept(new Insert(messager, maker, names));
break;
}
}
}
}
}
}
return false;
}
class Insert extends TreeTranslator {
private Messager messager;
private TreeMaker maker;
private Name.Table names;
public Insert(Messager messager, TreeMaker maker, Name.Table names) {
this.messager = messager;
this.maker = maker;
this.names = names;
}
@Override
public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
super.visitMethodDef(jcMethodDecl);
boolean isVisited = false;
if (jcMethodDecl.mods != null && jcMethodDecl.mods.annotations != null) {
List<JCTree.JCAnnotation> ans = jcMethodDecl.mods.annotations;
for (JCTree.JCAnnotation annotation : ans) {
if (annotation.annotationType.toString().equals("JavascriptInterface")) {
isVisited = true;
break;
}
}
}
if (isVisited) {
JCTree.JCExpression invokeMethod = maker.Apply(List.nil(),
maker.Select(maker.Ident(names.fromString("mWebActivity")),
names.fromString(
"getCurrentUrl")), List.nil());
JCTree.JCVariableDecl statement = makeVarDef(maker.Modifiers(0), "surl",
memberAccess("java.lang.String"), invokeMethod);
JCTree.JCExpression invokeMethod1 = maker.Apply(List.of(memberAccess("java.lang" +
".String"), memberAccess("java.lang.String")),
memberAccess("com.dxdxmm.app.component.web.WebBridgeHelperKt" +
".isHostVerify"), List.of(maker.Ident(names.fromString("surl")),
maker.Literal(jcMethodDecl.name.toString())));
JCTree.JCVariableDecl statement1 = makeVarDef(maker.Modifiers(0), "verify",
maker.TypeIdent(TypeTag.BOOLEAN), invokeMethod1);
JCTree.JCIf statement2 = maker.If(maker.Ident(names.fromString("verify")),
jcMethodDecl.body, maker.Skip());
JCTree.JCReturn statement3 = maker.Return(maker.Literal(false));
JCTree.JCReturn statement4 = maker.Return(maker.Literal(""));
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
if (!jcMethodDecl.name.toString().equals("<init>")) {
statements.append(statement);
statements.append(statement1);
statements.append(statement2);
if (jcMethodDecl.restype != null) {
if (jcMethodDecl.restype.toString().equals("String")) {
statements.append(statement4);
} else if (jcMethodDecl.restype.toString().equals("boolean")) {
statements.append(statement3);
}
}
}
JCTree.JCBlock body = maker.Block(0, statements.toList());
result = maker.MethodDef(
jcMethodDecl.getModifiers(),
names.fromString(jcMethodDecl.name.toString()),
jcMethodDecl.restype,
jcMethodDecl.typarams,
jcMethodDecl.params,
jcMethodDecl.thrown,
body,
jcMethodDecl.defaultValue
);
note(result.toString());
}
}
private JCTree.JCVariableDecl makeVarDef(JCTree.JCModifiers modifiers, String name,
JCTree.JCExpression vartype,
JCTree.JCExpression init) {
return maker.VarDef(
modifiers,
names.fromString(name),
vartype,
init
);
}
private JCTree.JCExpression memberAccess(String components) {
String[] componentArray = components.split("\\.");
JCTree.JCExpression expr = maker.Ident(names.fromString(componentArray[0]));
for (int i = 1; i < componentArray.length; i++) {
expr = maker.Select(expr, names.fromString(componentArray[i]));
}
return expr;
}
}
private void note(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, msg);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(WebModule.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
init:初始化。可以得到 ProcessingEnviroment,ProcessingEnviroment 提供很多有用的工具类 Elements, Types 和 Filer getSupportedAnnotationTypes:指定这个注解处理器是注册给哪个注解的,这里说明是注解 BindView getSupportedSourceVersion:指定使用的 Java 版本,通常这里返回 SourceVersion.latestSupported() process:可以在这里写扫描、评估和处理注解的代码,生成 Java 文件
Messager : 日志打印类,有利于分析 Trees : 抽象语法树 TreeMaker : 抽象语法树操作类 Name.Table : 命名表,可根据名称找到对应的方法,变量,类 TreeTranslator : 抽象语法树转换器,可修改方法,变量,类
其他类不熟悉的自行查询,这里就不一一介绍了。
完成了 Processor 的部分,基本就大功告成了。
apt-library 工具类 可选
在 App Module 的 build.gradle 中添加依赖
dependencies {
implementation project(':apt-annotation')
}
创建注解工具类 BindViewTools
public class BindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
```java
通过javapoet生成代码:
添加依赖
```java
dependencies {
implementation 'com.squareup:javapoet:1.10.0'
}
新建 ClassCreatorProxy
public class ClassCreatorProxy {
//省略部分代码...
/**
* 创建Java代码
* @return
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
*/
private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
}
使用自定义注解
使用工具类时:
public class TestActivity extends AppCompatActivity {
@BindView(R.id.test)
TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
BindViewTools.bind(this);
}
}
不使用工具类,只修改某个方法时:
@BindView()
public class TestActivity extends AppCompatActivity {
TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
}
@JavascriptInterface
public void test(){
}
}
业务咨询:https://soloist.pages.dev
