Annotation Processor

2021-12-16 17:34:51 浏览数 (1)

Annotation 和 Annotation Processor

要了解Annotation Processor,首先需要先了解什么是 AnnotationAnnotation : 是 Java 注解。 例如常见的 @Override @Nullable 等, 可以对类或者字段进行标记。 这些标记可以在反射时读取 或者 通过 Annotation Processor进行解析来自动生成一些对应的代码。

Annotation Processor: 注解处理器, 在代码编译前进行处理。 可以自动生成一些代码,来避免在编码时写一些重复代码, 例如findViewByid()

使用Annotation Processor的一些库: butterknife Dagger2 ...

这里通过一个学习的例子来了解Annotation Processor 的工作原理。

示例Demo

  1. Android Studio创建一个java library (lib_annotation), 用于自定义注解
  2. New -> Java Class -> 类型选择 Annotation

BindView.java

代码语言:javascript复制
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}



/*
*@Retention 是一个元注释,表明我们自定义注释的使用范围
*/
public enum RetentionPolicy {
    SOURCE,        //注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
    CLASS,      //注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期, 
    RUNTIME;   //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在, 运行时候可以配合反射一起使用

    private RetentionPolicy() {
    }
}


/*
*  @Target定义注解的作用目标
*/
public enum ElementType {
    TYPE,    //可以用在class上
    FIELD,   //字段
    METHOD,   // 方法  
    PARAMETER,  //方法参数
    CONSTRUCTOR, //构造函数
    LOCAL_VARIABLE,  //局部变量
    ANNOTATION_TYPE, //注解
    PACKAGE,   //包
    TYPE_PARAMETER,
    TYPE_USE;

    private ElementType() {
    }
}
  1. Android Studio再创建一个java library (lib_processor), 用于处理自定义注解

BindingProcessor.java

代码语言:javascript复制
public class BindingProcessor extends AbstractProcessor {

    Filer filer;
    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mMessager.printMessage(Diagnostic.Kind.NOTE,"-------process start----");
        /*roundEnv.getRootElements()会返回工程中所有的Class
        在实际应用中需要对各个Class先做过滤以提高效率,避免对每个Class的内容都进行扫描*/
        for (Element element : roundEnvironment.getRootElements()) {
            // 获取包名
            String packageStr = element.getEnclosingElement().toString();
            // 获取类名称
            String classStr = element.getSimpleName().toString();
            mMessager.printMessage(Diagnostic.Kind.WARNING,"packageStr:"   packageStr   ", classStr:"   classStr);


            // 需要自动生成类的类名称
            ClassName className = ClassName.get(packageStr, classStr   "$Binding");
            // 构造函数的builder
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(ClassName.get(packageStr, classStr), "activity");

            boolean hasBinding = false;


            for (Element enclosedElement : element.getEnclosedElements()) {
                BindView bindView = enclosedElement.getAnnotation(BindView.class);
                if (bindView != null) {
                    hasBinding = true;
                    constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
                            enclosedElement.getSimpleName(), bindView.value());
                }
            }

            TypeSpec builtClass = TypeSpec.classBuilder(className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(constructorBuilder.build())
                    .build();

            if (hasBinding) {
                try {
                    JavaFile.builder(packageStr, builtClass)
                            .build().writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }
}
  1. lib_processor 模块中向系统注册Annotation Process。

resources -> META-INF -> services -> javax.annotation.processing.Processor

代码语言:javascript复制
com.******.BindingProcessor   // 解析器的完整路径
  1. 自动生成的代码,可以通过反射进行调用。

BindViewUtils.java

代码语言:javascript复制
  public static void bind(Activity activity) {
        try {
            // new MainActivityBinding(activity);
            Class bindingClass = Class.forName(activity.getClass().getCanonicalName()   "$Binding");
            Class activityClass = Class.forName(activity.getClass().getCanonicalName());
            Constructor constructor = bindingClass.getDeclaredConstructor(activityClass);
            constructor.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();
        }
    }

可能遇到的问题

  • compileJava 编译出现警告
代码语言:javascript复制
> Task :lib_process:compileJava
警告: [options] 未与 -source 1.7 一起设置引导类路径
1 个警告

问题原因: 本机的jdk环境 与 工程的配置环境不匹配

解决方案:

  1. 查看本机的java 版本
代码语言:javascript复制
$ /usr/libexec/java_home -V
Matching Java Virtual Machines (1):
    1.8.0_191, x86_64:  "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home

/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home
  1. 修改工程配置中的Java 环境配置

build.gradle

代码语言:javascript复制
sourceCompatibility = "8"
targetCompatibility = "8"
  • 注册processor 没有生效 向系统注册Annotation Process时,如果Processor 中process.java 未被执行。

resources -> META-INF -> services -> javax.annotation.processing.Processor

请检查文件路径是否正确, 本人就因为路径 META-INF 写成了 META_INF 导致注册失败。

END!

0 人点赞