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

文章来源:w2solo

