褐言的Java探索之旅Android进阶之旅Android基础

注解处理器(Annotation Processor)简析

2017-04-04  本文已影响4837人  Whyn

概念

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

用途

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

例子

butterknifeDagger2EventBus......

Annotation Processor实质原理

** 编译期间根据注解(Annotation)获取相关数据 **

既然Annotation Processor是为了在编译期间获取注解(Annotation)相关内容,那么,具体的操作步骤要如何做呢:

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

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){
    }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { 
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
    }
}
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor

2.将自定义注解处理器和
META-INF/services/javax.annotation.processing.Processor打包成一个.jar文件。所以其目录结构大概如下所示:

MyProcessor.jar
    - com
        - example
            - MyProcessor.class

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

*** 建议直接采用@AutoService(Processor.class)进行自定义注解处理器注册,简洁方便 ***

  1. 经过前面3个步骤后,其实就已经算完成了自定义Annotation Processor。后面要做的就是在源码里面,在需要的地方写上我们自定义的注解就行了。

Demo

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

  1. 创建一个java library,其gradle配置如下:
apply plugin: 'java'

targetCompatibility = '1.7'
sourceCompatibility = '1.7'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.google.auto.service:auto-service:1.0-rc3'
}
  1. 自定义一个注解(Annotation),用于存储元数据
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}
  1. 创建一个自定义Annotation Processor继承于AbstractProcessor


@AutoService(Processor.class)
public class MyAnnotationProcessor 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 Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

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

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

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


            //因为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(enclosingElement, bindViewFiledClassType, bindViewFiledName, id);
            return true;
        }
        return false;
    }

    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();
    }


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

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

}

** 借助Messager,我们可以在编译时输出日志. **

  1. 使用注解,我们在Android工程中创建几个测试类,然后进行注解,如下所示:
public class MainActivity extends AppCompatActivity {

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

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

rebuild一下,可以在Gradle Console窗口中看到打印结果:


result

可以看到,我们成功的在编译期间获取了我们注解的相关数据。只要拿到了数据,那么你自己想干嘛就自己去弄吧 _
最后,我们根据注解获取到的数据还生成了一个java文件,其生成路径:app\build\generated\source\apt\debug\com\yn\annotationprocessdemo\ViewBinding.java
具体内容如下:

package com.yn.annotationprocessdemo;

//Auto generated by apt,do not modify!!

public class ViewBinding {

    public static void main(String[] args) {
        System.out.println("android.widget.TextView tv = 2131427422");
    }
}

附录:

compile 'com.google.auto.service:auto-service:1.0-rc3'
      apply plugin: 'com.android.application'

      android {
          compileSdkVersion 24
          buildToolsVersion "24.0.0"

          defaultConfig {
              applicationId "com.example.annotationprocessor"
              minSdkVersion 15
              targetSdkVersion 24
              versionCode 1
              versionName "1.0"
          }
           buildTypes {
              release {
                  minifyEnabled false
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
              }
          }

          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_7
              targetCompatibility JavaVersion.VERSION_1_7
          }
          //解决duplicate问题
          packagingOptions {
              exclude 'META-INF/services/javax.annotation.processing.Processor'
          }
          }
          dependencies {
          compile fileTree(dir: 'libs', include: ['*.jar'])
          testCompile 'junit:junit:4.12'
          compile 'com.android.support:appcompat-v7:24.0.0'
          compile project(path: ':annotationprocessor')
          }
上一篇下一篇

猜你喜欢

热点阅读