注解的作用

  1. 注解+ APT 技术,生成 Java 文件。如DataBinding、Hilt、ButterKnife 等框架
  2. 注解+反射+动态代码
  3. 注解定义常量(代替枚举)或者限制函数里参数的范围(@DrawableRes、@StringRes等)

    代替枚举

    Java 代码
  1. @IntDef({JavaSwitch.DISABLE, JavaSwitch.ENABLE})
  2. @Target(ElementType.PARAMETER)
  3. @Retention(AnnotationRetention.SOURCE)
  4. public @interface JavaSwitch {
  5. int DISABLE = 0;
  6. int ENABLE = 1;
  7. }

Kotlin 代码

  1. @IntDef(KotlinSwitch.DISABLE, KotlinSwitch.ENABLE)
  2. @Target(AnnotationTarget.VALUE_PARAMETER)
  3. @Retention(AnnotationRetention.SOURCE)
  4. annotation class KotlinSwitch {
  5. companion object {
  6. const val DISABLE = 0
  7. const val ENABLE = 1
  8. }
  9. }

具体使用:
image.png
但是该方案嵌套方法/函数调用时。as 不会报错
image.png

APT技术

这才是注解的『最终归宿』,全程 Annotation Processing Tool 即注解处理器。简单来讲:在编译期识别自定义的注解然后根据规则生成对应代码。如:ButterKnife
现在利用 APT 技术来实现 ButterKnife 的 BindView 功能(自动FindViewById)

  • 创建一个 Java Module 名为 apt-test-annotation
  • 创建一个 Java Module 名为 apt-test-processor
  • 创建一个 Android Module 名为 apt-test-api

image.png

  1. @Target(ElementType.FIELD)//能修饰参数
  2. @Retention(RetentionPolicy.CLASS)//表示注解只在源码中存在,编译成 class 之后,就没了
  3. public @interface BindView {
  4. int value();
  5. }

�在 apt-test-annotation 的 gradle 文件里需要引入

  1. // 不引用无法执行注解执行器
  2. implementation 'com.google.auto.service:auto-service:1.0-rc6'
  3. annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
  4. // 以及需要引用 apt-test-processor。因为注解处理需需要使用这个注解
  5. implementation project(path: ":apt-test-annotation")

重点是 AptTestAnnotationProcessor这个类

  1. @AutoService(Processor.class)
  2. public class AptTestAnnotationProcessor extends AbstractProcessor {
  3. /**
  4. * 文件生成器
  5. */
  6. private Filer mFiler;
  7. /**
  8. * 日志
  9. */
  10. private Messager mMessager;
  11. @Override
  12. public synchronized void init(ProcessingEnvironment processingEnv) {
  13. super.init(processingEnv);
  14. mFiler = processingEnv.getFiler();
  15. mMessager = processingEnv.getMessager();
  16. }
  17. @Override
  18. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
  19. mMessager.printMessage(Diagnostic.Kind.NOTE, "注解处理器运行了");
  20. if (set == null || set.isEmpty()) {
  21. return false;
  22. }
  23. // 读取 BindView 注解
  24. Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);
  25. // 存储每个Activity中的所有注解属性
  26. HashMap<String, List<VariableElement>> annotatedMap = new HashMap<>();
  27. if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {
  28. for (Element element : elementsAnnotatedWith) {
  29. // 转成属性节点
  30. VariableElement e = (VariableElement) element;
  31. // 获取上一个节点,也就是类名
  32. TypeElement typeElement = (TypeElement) e.getEnclosingElement();
  33. // 获取类名
  34. String activityName = typeElement.getSimpleName().toString();
  35. //缓存对应类的所有节点
  36. List<VariableElement> variableElementList = annotatedMap.get(activityName);
  37. if (variableElementList == null) {
  38. variableElementList = new ArrayList<>();
  39. annotatedMap.put(activityName, variableElementList);
  40. }
  41. // 缓存所有的属性注解节点
  42. variableElementList.add(e);
  43. }
  44. }
  45. if (annotatedMap.size() > 0) {
  46. Writer writer = null;
  47. for (String activityName : annotatedMap.keySet()) {
  48. // 得到类上的所有节点
  49. List<VariableElement> variableElements = annotatedMap.get(activityName);
  50. //得到包名
  51. String packageName = processingEnv.getElementUtils().getPackageOf(variableElements.get(0)).toString();
  52. // 自定义类名,仿造 Butterknife
  53. String newClass = activityName + "_ViewBinding";
  54. try {
  55. // 创建java文件
  56. JavaFileObject javaFile = mFiler.createSourceFile(packageName + "." + newClass);
  57. // 写入代码
  58. writer = javaFile.openWriter();
  59. StringBuilder buffer = new StringBuilder();
  60. // 拼接代码
  61. buffer.append("package ").append(packageName).append(";\n");
  62. buffer.append("import android.view.View;\n");
  63. buffer.append("public class ").append(newClass).append("{\n");
  64. buffer.append("public ").append(newClass).append("(final ").append(packageName).append(".").append(activityName).append(" target){\n");
  65. for (VariableElement element : variableElements) {
  66. String fieldName = element.getSimpleName().toString();
  67. int resId = element.getAnnotation(BindView.class).value();
  68. // 执行findViewById
  69. buffer.append("target.").append(fieldName).append("=target.findViewById(").append(resId).append(");\n");
  70. }
  71. buffer.append("}\n}");
  72. writer.write(buffer.toString());
  73. } catch (IOException e) {
  74. e.printStackTrace();
  75. } finally {
  76. try {
  77. if (writer != null) {
  78. writer.close();
  79. }
  80. } catch (IOException e) {
  81. e.printStackTrace();
  82. }
  83. }
  84. }
  85. }
  86. return true;
  87. }
  88. /**
  89. * 当前注解处理器支持的注解集合,如果支持,就会调用 process 方法
  90. *
  91. * @return 支持的注解集合
  92. */
  93. @Override
  94. public Set<String> getSupportedAnnotationTypes() {
  95. HashSet<String> types = new HashSet<>();
  96. types.add(BindView.class.getCanonicalName());
  97. return types;
  98. }
  99. /**
  100. * 编译当前注解处理器的 JDK 版本
  101. *
  102. * @return JDK 版本
  103. */
  104. @Override
  105. public SourceVersion getSupportedSourceVersion() {
  106. return SourceVersion.latestSupported();
  107. }
  108. }

�编译项目就会生成代码
image.png
生成代码其实是靠 Filer 这个类,但是代码里的方式不太灵活。可以使用 JavaPoet 库来帮助生成代码
PS:在主模块别忘了引用三个 Module