经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
如何使用Android注解处理器
来源:jb51  时间:2021/4/12 19:00:16  对本文有异议

我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器。

注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。

本篇用我之前写的Saber举例说明。

1.定义注解

推荐New -> Module -> Java Library,新建一个Java Library Module,命名为xx-annotation。用来单独存放注解。

既然是注解处理器,那么首先需要有注解。自定义一个注解使用@interface关键字。

  1. public @interface LiveData {
  2. }

然后我们需要用到注解的注解,也就是元注解来控制注解的行为。这里我简单介绍一些元注解。

  • Retention 表示注解的保留范围。值用RetentionPolicy枚举类型表示,分为CLASSRUNTIMESOURCE
  • Target 表示注解的使用范围。值用ElementType枚举类型表示,有TYPE(作用于类)、FIELD(作用于属性)、METHOD(作用于方法)等。

这里我的@LiveData注解作用是为了便于创建LiveData,而创建时需要知道数据类型。所以这个注解的使用范围就是类和属性。

其次这个注解处理生成模板代码后,我们不需要保留在编译后的.class文件中。所以可以使用SOURCE

  1. @Retention(RetentionPolicy.SOURCE)
  2. @Target({ElementType.FIELD, ElementType.TYPE})
  3. public @interface LiveData {
  4. }

2.实现处理器

首先New -> Module -> Java Library,新建一个Java Library Module,命名为xx-complier。用来存放注解处理器。

创建一个继承AbstractProcessor的类LiveDataProcessor

  1. public class LiveDataProcessor extends AbstractProcessor {
  2.  
  3. @Override
  4. public SourceVersion getSupportedSourceVersion() {
  5. return SourceVersion.latestSupported();
  6. }
  7.  
  8. @Override
  9. public Set<String> getSupportedAnnotationTypes() {
  10. return Collections.singleton(LiveData.class.getCanonicalName());
  11. }
  12.  
  13. @Override
  14. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  15. return false;
  16. }
  17. }

需要实现三个方法,getSupportedSourceVersion 指定支持的Java版本,getSupportedAnnotationTypes指定处理的注解。process是处理注解的地方。

不过这里还需要初始化一些工具,可以重写init 来实现。

  1. private Elements elementUtils; // 操作元素的工具类
  2. private Filer filer; // 用来创建文件
  3. private Messager messager; // 用来输出日志、错误或警告信息
  4.  
  5. @Override
  6. public synchronized void init(ProcessingEnvironment processingEnv) {
  7. super.init(processingEnv);
  8. this.elementUtils = processingEnv.getElementUtils();
  9. this.filer = processingEnv.getFiler();
  10. this.messager = processingEnv.getMessager();
  11. }

下面就是重点process了,我们的注解作用范围是类和属性。所以我们需要将同一个类下的注解整理到一起。这里使用getElementsAnnotatedWith循环所有注解元素。

  1. @Override
  2. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  3.  
  4. for (Element element : roundEnv.getElementsAnnotatedWith(LiveData.class)) {
  5. if (element.getKind() == ElementKind.FIELD) {
  6. handlerField((VariableElement) element); // 表示一个字段
  7. }
  8. if (element.getKind() == ElementKind.CLASS) {
  9. handlerClass((TypeElement) element); // 表示一个类或接口
  10. }
  11. // ExecutableElement表示某个类或接口的方法
  12. }
  13. return true;
  14. }
  15.  
  16. private void handlerClass(TypeElement element) {
  17. ClassEntity classEntity = new ClassEntity(element);
  18. String className = element.getSimpleName().toString();
  19.  
  20. if (classEntityMap.get(className) == null) {
  21. classEntityMap.put(className, classEntity);
  22. }
  23. }
  24.  
  25. private void handlerField(VariableElement element) {
  26. FieldEntity fieldEntity = new FieldEntity(element);
  27. String className = fieldEntity.getClassSimpleName();
  28. if (classEntityMap.get(className) == null) {
  29. classEntityMap.put(className,
  30. new ClassEntity((TypeElement) element.getEnclosingElement()));
  31. }
  32. ClassEntity classEntity = classEntityMap.get(className);
  33. classEntity.addFieldEntity(fieldEntity);
  34. }

上面代码中的element.getKind()获取的是元素种类,对应的基本是上面元注解ElementType的类型。

ElementType ElementKind Element
TYPE CLASS TypeElement
FIELD FIELD VariableElement
METHOD METHOD ExecutableElement

下面是封装的简易element,便于实际的使用。

  1. class ClassEntity {
  2. private final TypeElement element;
  3. private final Name classSimpleName;
  4. private final Map<String, FieldEntity> fields = new HashMap<>();
  5.  
  6. public ClassEntity(TypeElement element) {
  7. this.element = element;
  8. this.classSimpleName = element.getSimpleName();
  9. }
  10.  
  11. public String getClassSimpleName() {
  12. return classSimpleName.toString();
  13. }
  14.  
  15. public void addFieldEntity(FieldEntity fieldEntity) {
  16. String fieldName = fieldEntity.getElement().toString();
  17. if (fields.get(fieldName) == null) {
  18. fields.put(fieldName, fieldEntity);
  19. }
  20. }
  21.  
  22. public TypeElement getElement() {
  23. return element;
  24. }
  25.  
  26. public Map<String, FieldEntity> getFields() {
  27. return fields;
  28. }
  29. }
  30.  
  31. class FieldEntity {
  32. private VariableElement element;
  33. private String classSimpleName;
  34.  
  35. public FieldEntity(VariableElement element) {
  36. this.element = element;
  37. this.classSimpleName = element.getEnclosingElement().getSimpleName().toString();
  38. }
  39. public VariableElement getElement() {
  40. return element;
  41. }
  42.  
  43. public String getClassSimpleName() {
  44. return classSimpleName;
  45. }
  46. }

下面就是使用JavaPoet来生成代码,具体使用见JavaPoet使用攻略。这部分直接上代码:

  1. private JavaFile brewViewModel(Map.Entry<String, ClassEntity> item) {
  2. ClassEntity classEntity = item.getValue();
  3. LiveData liveData = classEntity.getElement().getAnnotation(LiveData.class);
  4. /*类名*/
  5. String className = classEntity.getElement().getSimpleName().toString() + "ViewModel";
  6.  
  7. ClassName viewModelClazz = ClassName.get("androidx.lifecycle", "ViewModel");
  8.  
  9.  
  10. TypeSpec.Builder builder = TypeSpec
  11. .classBuilder(className)
  12. .addModifiers(Modifier.PUBLIC)
  13. .superclass(viewModelClazz);
  14.  
  15. // 优先执行类LiveData注解
  16. if (liveData != null){
  17. TypeName valueTypeName = ClassName.get(classEntity.getElement());
  18. brewLiveData(classEntity.getClassSimpleName(), valueTypeName, builder);
  19. }else {
  20. Map<String, FieldEntity> fields = classEntity.getFields();
  21.  
  22. for (FieldEntity fieldEntity : fields.values()){
  23. String fieldName = StringUtils.upperCase(fieldEntity.getElement().getSimpleName().toString());
  24. TypeName valueTypeName = ClassName.get(fieldEntity.getElement().asType());
  25. brewLiveData(fieldName, valueTypeName, builder);
  26. }
  27. }
  28.  
  29. TypeSpec typeSpec = builder.build();
  30. // 指定包名
  31. return JavaFile.builder("com.zl.weilu.saber.viewmodel", typeSpec).build();
  32. }
  33.  
  34. private void brewLiveData(String fieldName, TypeName valueTypeName, TypeSpec.Builder builder){
  35.  
  36. String liveDataType;
  37. ClassName liveDataTypeClassName;
  38.  
  39. liveDataType = "m$L = new MutableLiveData<>()";
  40. liveDataTypeClassName = ClassName.get("androidx.lifecycle", "MutableLiveData");
  41.  
  42. ParameterizedTypeName typeName = ParameterizedTypeName.get(liveDataTypeClassName, valueTypeName);
  43.  
  44. FieldSpec field = FieldSpec.builder(typeName, "m" + fieldName, Modifier.PRIVATE)
  45. .build();
  46.  
  47. MethodSpec getMethod = MethodSpec
  48. .methodBuilder("get" + fieldName)
  49. .addModifiers(Modifier.PUBLIC)
  50. .returns(field.type)
  51. .beginControlFlow("if (m$L == null)", fieldName)
  52. .addStatement(liveDataType, fieldName)
  53. .endControlFlow()
  54. .addStatement("return m$L", fieldName)
  55. .build();
  56.  
  57. MethodSpec getValue = MethodSpec
  58. .methodBuilder("get" + fieldName + "Value")
  59. .addModifiers(Modifier.PUBLIC)
  60. .returns(valueTypeName)
  61. .addStatement("return this.$N().getValue()", getMethod)
  62. .build();
  63.  
  64. MethodSpec setMethod = MethodSpec
  65. .methodBuilder("set" + fieldName)
  66. .addModifiers(Modifier.PUBLIC)
  67. .returns(void.class)
  68. .addParameter(valueTypeName, "mValue")
  69. .beginControlFlow("if (this.m$L == null)", fieldName)
  70. .addStatement("return")
  71. .endControlFlow()
  72. .addStatement("this.m$L.setValue(mValue)", fieldName)
  73. .build();
  74.  
  75. MethodSpec postMethod = MethodSpec
  76. .methodBuilder("post" + fieldName)
  77. .addModifiers(Modifier.PUBLIC)
  78. .returns(void.class)
  79. .addParameter(valueTypeName, "mValue")
  80. .beginControlFlow("if (this.m$L == null)", fieldName)
  81. .addStatement("return")
  82. .endControlFlow()
  83. .addStatement("this.m$L.postValue(mValue)", fieldName)
  84. .build();
  85.  
  86. builder.addField(field)
  87. .addMethod(getMethod)
  88. .addMethod(getValue)
  89. .addMethod(setMethod)
  90. .addMethod(postMethod);
  91.  
  92. }

输出文件:

  1. @Override
  2. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  3. ...
  4. for (Map.Entry<String, ClassEntity> item : classEntityMap.entrySet()) {
  5. try {
  6. brewViewModel(item).writeTo(filer);
  7. } catch (Exception e) {
  8. messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), item.getValue().getElement());
  9. }
  10. }
  11. return true;
  12. }

3.注册处理器

注册处理器才可以使处理器生效,使用Google开源的AutoService的库。

  1. dependencies {
  2. implementation 'com.squareup:javapoet:1.13.0'
  3. implementation 'com.google.auto.service:auto-service:1.0-rc7'
  4. annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
  5. }

然后添加@AutoService注解即可。

  1. @AutoService(Processor.class)
  2. public class LiveDataProcessor extends AbstractProcessor {
  3. }

4.调试注解处理器

项目的gradle.properties中配置:

  1. org.gradle.daemon=true
  2. org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

接着Run -> Edit Configurations -> 点击左上角加号 -> 选择 Remote -> 指定module(可选)

注意两个端口号一致。然后选择添加的“APT”,点击debug按钮启动。

后面就是打断点,编译项目即可。

5.支持增量编译

Gradle 在 5.0 增加了对 Java 增量编译的支持,通过增量编译,我们能够获得一些优点:

  • 更少的编译耗时
  • 更少的字节码修改

如果注解处理器没有支持增量编译,那么编译时,会输出以下日志:

  1. w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.x.XXProcessor (NON_INCREMENTAL).

Gradle 支持两种注解处理器的增量编译:isolating 和 aggregating。

支持方法是在 META-INF/gradle/incremental.annotation.processors 文件中声明支持增量编译的注解处理器。

  1. xx-complier/src/main/
  2. ├── java
  3. ...
  4. └── LiveDataProcessor
  5. └── resources
  6. └── META-INF
  7. ├── gradle
  8. └── incremental.annotation.processors
  9. └── services
  10. └── javax.annotation.processing.Processor

incremental.annotation.processors内容如下:

  1. com.zl.weilu.saber.compiler.LiveDataProcessor,aggregating

这部分详细内容见 让 Annotation Processor 支持增量编译。

6.使用处理器

添加依赖:

  1. dependencies {
  2. implementation project(':saber-annotation')
  3. annotationProcessor project(':saber-compiler')
  4. }

首先创建一个类,使用@LiveData注解标记你要保存的数据。

  1. public class SeekBar {
  2.  
  3. @LiveData
  4. Integer value;
  5. }

Build – > Make Project 生成代码如下:

  1. public class SeekBarViewModel extends ViewModel {
  2. private MutableLiveData<Integer> mValue;
  3.  
  4. public MutableLiveData<Integer> getValue() {
  5. if (mValue == null) {
  6. mValue = new MutableLiveData<>();
  7. }
  8. return mValue;
  9. }
  10.  
  11. public Integer getValueValue() {
  12. return getValue().getValue();
  13. }
  14.  
  15. public void setValue(Integer mValue) {
  16. if (this.mValue == null) {
  17. return;
  18. }
  19. this.mValue.setValue(mValue);
  20. }
  21.  
  22. public void postValue(Integer mValue) {
  23. if (this.mValue == null) {
  24. return;
  25. }
  26. this.mValue.postValue(mValue);
  27. }
  28. }

至此,我们就完成了一个简单的自定义处理器。

以上就是如何使用Android注解处理器的详细内容,更多关于Android注解处理器的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号