概述
注解处理器(Annotation Processor),是javac
的一个工具,用来在编译时扫描和处理注解。
一个注解处理器以Java代码(或者编译过得字节码)作为输入,生成.java
文件作为输出。
接下来我们模仿ButterKnife 实现一个@BindView
的注解来了解Java注解处理器的使用。
创建项目
模块说明:
bindview-annotation
:定义注解,@BindView
。bindview-compiler
:定义注解处理器,处理被@BindView
标记的代码,并在编译时生成xxxActivity_ViewBinding.java
bindview-api
:工具类,调用xxxActivity_ViewBinding.java
中的方法,实现View
的绑定。
bindview-annotation(自定义注解) 创建注解类BindView
1 2 3 4 5 @Retention (RetentionPolicy.CLASS) @Target (ElementType.FIELD) public @interface BindView { int value () ; }
bindview-compiler(注解处理器) 在该module的build.gradle
中添加如下代码:
1 2 3 4 dependencies { implementation 'com.google.auto.service:auto-service:1.0-rc3' implementation project(':bindview-annotation' ) }
创建BindViewProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 @AutoService (Processor.class)public class BindViewProcessor extends AbstractProcessor { private Messager mMessager; private Elements mElementUtils; private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); @Override public synchronized void init (ProcessingEnvironment processingEnvironment) { super .init(processingEnvironment); mMessager = processingEnvironment.getMessager(); mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes () { return Collections.singleton(BindView.class.getCanonicalName()); } @Override public SourceVersion getSupportedSourceVersion () { return SourceVersion.latestSupported(); } @Override public boolean process (Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { mMessager.printMessage(Diagnostic.Kind.NOTE, "processing..." ); mProxyMap.clear(); Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); String fullClassName = classElement.getQualifiedName().toString(); ClassCreatorProxy proxy = mProxyMap.get(fullClassName); if (proxy == null ) { proxy = new ClassCreatorProxy(mElementUtils, classElement); mProxyMap.put(fullClassName, proxy); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int id = bindAnnotation.value(); proxy.putElement(id, variableElement); } for (String key : mProxyMap.keySet()) { ClassCreatorProxy proxyInfo = mProxyMap.get(key); mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName()); try { JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement()); Writer writer = jfo.openWriter(); writer.write(proxyInfo.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + " error!" ); } } mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ..." ); return true ; } }
ClassCreatorProxy
是创建Java代码的代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class ClassCreatorProxy { private String mBindingClassName; private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); public ClassCreatorProxy (Elements elementUtils, TypeElement classElement) { mTypeElement = classElement; PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); mPackageName = packageName; mBindingClassName = className + "_ViewBinding" ; } public void putElement (int id, VariableElement element) { mVariableElementMap.put(id, element); } public String generateJavaCode () { StringBuilder builder = new StringBuilder(); builder.append("package " ).append(mPackageName).append(";\n\n" ); builder.append("\n" ); builder.append("public class " ).append(mBindingClassName); builder.append(" {\n" ); generateMethods(builder); builder.append("\n" ); builder.append("}\n" ); return builder.toString(); } private void generateMethods (StringBuilder builder) { builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host) {\n" ); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append("host." + name).append(" = " ); builder.append("(" + type + ")(((android.app.Activity)host).findViewById(" + id + "));\n" ); } builder.append(" }\n" ); } public String getProxyClassFullName () { return mPackageName + "." + mBindingClassName; } public TypeElement getTypeElement () { return mTypeElement; } }
bindview-api(注解生成代码的调用工具类) 创建注解工具类BindViewTool
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class BindViewTool { public static void bind(Activity activity) { Class clz = activity.getClass(); try { Class bindViewClass = Class.forName(clz.getName() + "_ViewBinding" ); Method method = bindViewClass.getMethod("bind" , activity.getClass()); method.invoke(bindViewClass.newInstance(), activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
使用注解 在app模块的build.gradle
中添加如下代码:
1 2 3 4 5 6 7 dependencies { implementation project(':bindview-annotation' ) implementation project(':bindview-api' ) annotationProcessor project(':bindview-compiler' ) }
在MainActivity中使用注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MainActivity extends AppCompatActivity { @BindView (R.id.content) TextView mContentText; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); BindViewTool.bind(this ); mContentText.setText("From BindView" ); } }
运行后生成的代码(路径 app/build/generated/source/apt
):
MainActivity_ViewBinding
代码:
1 2 3 4 5 public class MainActivity_ViewBinding {public void bind (com.github.xch168.annotationdemo.MainActivity host) { host.mContentText = (android.widget.TextView)(((android.app.Activity)host).findViewById(2131165228 )); } }
通过javapoet生成代码 上面生成代码的部分,是通过字符串拼接,过程非常繁琐。接下来就介绍一种更优雅的方式,使用javapoet
。
添加依赖:
1 2 3 dependencies { implementation 'com.squareup:javapoet:1.10.0' }
在ClassCreatorProxy
中添加如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class ClassCreatorProxy { public TypeSpec generateJavaCodeByJavapoet () { TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName) .addModifiers(Modifier.PUBLIC) .addMethod(generateMethodsByJavapoet()) .build(); return bindingClass; } private MethodSpec generateMethodsByJavapoet () { 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; } }
在BindViewProcessor
中调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public boolean process (Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { for (String key : mProxyMap.keySet()) { ClassCreatorProxy proxyInfo = mProxyMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCodeByJavapoet()).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ..." ); }
相比用StringBuilder拼Java代码,明显简介很多,且生成的代码是一样的。
参考文章
【Android】APT
自定义Java注解处理器
一小时搞明白注解处理器(Annotation Processor Tool)
Android APT及基于APT的简单应用
Android 编译时注解-提升-butterknife
javapoet