注解Annotation Processor学习

概念

注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。

用途

由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。

例子

butterknife,Dagger2,EventBus…

注解的分类

标注注解

  • @Overrride:对覆盖超类中的方法进行标记,如果被标记的方 法并没有实际覆盖超类中的方法,则编译器会发出警告。
  • @Deprecate:提示开发者该方法已经废弃,不推荐使用。
  • @SupperWarnings:选择性地取消特定代码中的警告。

元注解

用来标注其它注解而创建的新注解,元注解的类型有以下几种:

  • @Target:注解所修饰的对象范围
  • @Inherited:表示注解可以被继承
  • @Documented:表示这个注解应该被JavaDoc工具记录
  • @Rentation:用来声明注解的保留策略
  • @Repeable:JDK8新增,允许一个注解在同一声明类型(类,属性或方法)上多次使用。

其中@Target注解的取值是一个ElementType类型的枚举,其中有一下几种取值,对应不同的对象范围。

  • ElementType.TYPE:能修饰类,接口或者枚举类型。
  • ElementType.FIELD:能修饰成员变量
  • ElementType. METHOD:能修饰方法
  • ElementType. PARAMETER:能修饰参数
  • ElementType. CONSTRUCTOR:能修饰构造方法
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType. ANNOTATION_TYPE:能修饰注解
  • ElementType. PACKAGE:能修饰包
  • ElementType.TYPE_PARAMETER:能修饰参数声明
  • ElementType.TYPE_USE:使用类型

其中@Retention注解有3种类型,分别表示不同级别的保留策略。

  • RetentionPolicy.SOURCE:源码级注解,注解信息只会保留在Java源码中,源码在编译后,注解信息将会被丢弃,不会保留在.class文件中。
  • RetentionPolicy.CLASS:编译时注解。注解信息会保留在.java源码以及.class中。当运行java程序时,JVM会丢弃该注解信息,不会保留在JVM中,许多注解类框架,如EventBus,ButterKnife他们都是使用编译时注解信息开发的,针对编译时注解我们采用AbstractProcessor来处理注解信息。
  • RetentionPolicy.RUNTIME:运行注解。当运行Java程序时,JVM也会保留该注解信息,可以通过反射获取该注解信息。

注解和注解处理

  1. Android Studio创建一个java library,叫做AnnotationLib
  2. 自定义一个注解(Annotation),用于存储元数据
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}
  1. 创建一个自定义Annotation Processor继承于AbstractProcessor
 @AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Messager mMessager;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        Set bindViewElements = roundEnvironment.getElementsAnnotatedWith
                (BindView.class);
        for (Element element : bindViewElements) {
            //1.获取包名
            PackageElement packageElement = mElementUtils.getPackageOf(element);
            String packgeName = packageElement.getQualifiedName().toString();
            note(String.format("package = %s", packgeName));

            //2.获取包装类类型
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            String ecclosingName = typeElement.getQualifiedName().toString();
            note(String.format("enclosindClass = %s", ecclosingName));

            //因为BindView只作用于filed,所以这里可直接进行强转
            VariableElement bindViewElement = (VariableElement) element;
            //3.获取注解的成员变量名
            String bindViewFiledName = bindViewElement.getSimpleName().toString();
            //3.获取注解的成员变量类型
            String bindViewFiledClassType = bindViewElement.asType().toString();

            //4.获取注解元数据
            BindView bindView = element.getAnnotation(BindView.class);
            int id = bindView.value();
            note(String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id));

            //4.生成文件
            createFile(typeElement, bindViewFiledClassType, bindViewFiledName, id);
        }
        return false;
    }

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

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    private void note(String msg) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

    private void createFile(TypeElement enclosingElement, String bindViewFiledClassType, String bindViewFiledName, int id) {
        String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
        try {
            JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding", new Element[]{});
            Writer writer = jfo.openWriter();
            writer.write(brewCode(pkName, bindViewFiledClassType, bindViewFiledName, id));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String brewCode(String pkName, String bindViewFiledClassType, String bindViewFiledName, int id) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("//Auto generated by apt,do not modify!!\n\n");
        builder.append("public class ViewBinding { \n\n");
        builder.append("public static void main(String[] args){ \n");
        String info = String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id);
        builder.append("System.out.println(\"" + info + "\");\n");
        builder.append("}\n");
        builder.append("}");
        return builder.toString();
    }
}

@AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。

  • init(ProcessingEnvironment env):每个Annotation Processor必须***
    有一个空的构造函数 *。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements , Types , Filer **等等
  • process(Set annoations, RoundEnvironment roundEnv):Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.
  • getSupportedAnnotationTypes(): 该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)
  • getSupportedSourceVersion():用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:
    return SourceVersion.RELEASE_7;
  1. 经过前面3个步骤后,其实就已经算完成了自定义Annotation Processor。后面要做的就是在源码里面,在需要的地方写上我们自定义的注解就行了。

DEMO中使用

牢记Annotation Process的实质用处就是在编译时通过注解获取相关数据,
那么,在这个Demo里面,我们就直接在编译时打印出我们注解的数据的成员变量名,成员变量类,包装类类名,包名和注解元数据进行显示,然后将这些信息写入到一个.java文件中,这里我就简单的直接输出这些信息进行显示。
按照上面自定义注解处理的方法,我们操作如下:

  1. 创建一个module,其主要gradle配置如下:
apply plugin: 'com.android.application'

android {
    ...

    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath = true
            }
        }
    }
}

dependencies {
   ...
    implementation project(':AnnotationLib')
}
  1. 在activity中使用
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

编译时在console中输出:
在这里插入图片描述
最后,我们根据注解获取到的数据还生成了一个java文件,其生成路径:app\build\generated\source\apt\debug\com\hiten\test\ViewBinding.java
具体内容如下:
注解Annotation Processor学习_第1张图片

异常处理

  1. Annotation processors must be explicitly declared now
在app的build中 android {
    ...
    defaultConfig {
        ...
        //添加如下配置就OK了 
		javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath = true
            }
        }
    }

参考

注解处理器(Annotation Processor)简析
编译时注解处理器AnnotationProcessor的使用

你可能感兴趣的:(android学习)