编译时注解二

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/y331271939/article/details/81773012

编译注解

上篇文章说了一下编译时注解的用法和使用步骤,并写了一个Demo编译时生成本地文件,这篇文章会通过编译时注解生成一个Java类看一下编译时注解在安卓中的具体使用.

1、新建Android工程名为 AnnotationPractice,完成后,新建一个Module选择Java Library 名为 testlib.
2、在Java工程中定义注解类GenerateCode.
@Documented()
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface GenerateCode {
}
3、在Java工程中定义注解处理器GenerateProcessor
public class GenerateProcessor extends AbstractProcessor {
    private Messager messager; // 输入日志
    private Filer filer; // 生成文件
    private Elements elementsUtils; // Element工具

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(GenerateCode.class.getCanonicalName());
    }

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        elementsUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);

        if (elements.iterator().hasNext()) {
            Element element = elements.iterator().next(); // 获取set集合的第一个元素
            if (ElementKind.CLASS == element.getKind()) {
                TypeElement typeElement = (TypeElement) element;
                final String pkgName = getPackageName(typeElement); // 获取包名
                messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义

                final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名

                String code = jointCode(pkgName, clsName);

                Writer writer = null;
                try {
                    JavaFileObject file = filer.createSourceFile(pkgName + "." + clsName);
                    writer = file.openWriter();
                    writer.write(code);

                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (writer != null)
                            writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }


            }
        }

        return true;
    }

    /**
     * 拼接代码
     *
     * @param pkgName 包名
     * @param clsName 类名
     * @return  拼接效果下面这样
     *
      package pkgName;

      public class clsName {
          public void print() {
             System.out.println("Hello");
          }
      }
     */
    private String jointCode(String pkgName, String clsName) {
        StringBuilder builder = new StringBuilder();
        builder.append("package ");
        builder.append(pkgName);
        builder.append(";");
        builder.append("\n\n");
        builder.append("public class ");
        builder.append(clsName);
        builder.append(" {");
        builder.append("\n");
        builder.append("\tpublic void print() {");
        builder.append("\n");
        builder.append("\t\tSystem.out.println(\"Hello\");");
        builder.append("\n");
        builder.append("\t}");
        builder.append("\n");
        builder.append("}");

        return builder.toString();
    }

    private String getPackageName(TypeElement type) {
        return elementsUtils.getPackageOf(type).getQualifiedName().toString();
    }

}
4、在main下新建resources文件夹,在resources下新建META-INF文件夹,在META-INF下新建services文件夹,在services下新建文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中写如文件注解器全限定名
com.example.bill.testlib.GenerateProcessor
5、编译. 如果配置好gradle环境变量,在Java Module根目录下,即testlib下使用 gradle build 命令编译,编译成功后会在testlib的build下生成jar包(AnnotationPractice/testlib/build/libs/testlib.jar).
6、在app中使用
  • 将编译好的testlib.jar拷贝到app的libs下,在app的build.gradle下dependencies中引入jar包
annotationProcessor files('libs/testlib.jar')
  • 在app中使用注解,在MainActivity上使用注解GenerateCode
package com.bill.annotationpractice;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.example.bill.testlib.GenerateCode;

@GenerateCode
public class MainActivity extends AppCompatActivity {

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

    }

}

7、编译. 在app目录下使用 gradle build 命令编译,编译成功后会在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成我们编写的MainActivity G A M a i n A c t i v i t y GA类,MainActivity GA就可以在app中使用了.生成的类如下
package com.bill.annotationpractice;

public class MainActivity$GA {
	public void print() {
		System.out.println("Hello");
	}
}

eg:AnnotationPractice-branch4

上面代码通过编译时生成了Java文件,但是需要手动拼接生成的代码和注解处理器的配置信息.下面介绍两个开源库开简化这两步,Google和Square开源的两个库分别用来生成注解处理器的配置信息和生成Java类的

具体使用如下
  • 在testlib的build.gradle中的dependencies下引入
dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'
}
  • 注解处理器类上使用@AutoService(Processor.class),他是Google提供的作用是生成注解处理器的配置信息,代替上面的第4步的

  • 在注解处理器中使用javapoet中的api生成代码

    MethodSpec methodSpec = MethodSpec.methodBuilder("print")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) // 方法修饰符
            .returns(void.class) // 方法返回值
            .addStatement("$T.out.println($S)", // 参数 System.class, "Hello") // 内容
            .build();
    TypeSpec typeSpec = TypeSpec.classBuilder(clsName) // 类名
            .addModifiers(Modifier.PUBLIC) // 类修饰符
            .addMethod(methodSpec) //类中的方法
            .build();
    JavaFile javaFile = JavaFile.builder(pkgName, typeSpec) // 包名和类
            .build();
    try {
        javaFile.writeTo(processingEnv.getFiler());
    } catch (IOException e) {
        e.printStackTrace();
    }

下面通过这两个库来替换上面的Demo

上面第1步和第2步不变,将第3步和第4步去掉,注解处理器改为下面
@AutoService(Processor.class)
public class GenerateProcessor extends AbstractProcessor {

    private Messager messager; // 输入日志
    private Filer filer; // 生成文件
    private Elements elementsUtils; // Element工具

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(GenerateCode.class.getCanonicalName());
    }

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        elementsUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);

        if (elements.iterator().hasNext()) {
            Element element = elements.iterator().next(); // 获取set集合的第一个元素
            if (ElementKind.CLASS == element.getKind()) {
                TypeElement typeElement = (TypeElement) element;
                final String pkgName = getPackageName(typeElement); // 获取包名
                messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义

                final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名


                MethodSpec methodSpec = MethodSpec.methodBuilder("print")
                        .addModifiers(Modifier.PUBLIC)
                        .returns(void.class)
                        .addStatement("$T.out.println($S)", System.class, "Hello")
                        .build();
                TypeSpec typeSpec = TypeSpec.classBuilder(clsName)
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(methodSpec)
                        .build();
                JavaFile javaFile = JavaFile.builder(pkgName, typeSpec)
                        .build();
                try {
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

        return true;
    }

    private String getPackageName(TypeElement type) {
        return elementsUtils.getPackageOf(type).getQualifiedName().toString();
    }


}
还是上面第5步编译生成jar包,上面第6步在app中引入使用,第7步中编译,然后会报下面错误,就是找不到javapoet包,因为上面编译生成jar并没有将javapoet的代码打到包里,所以找不到

s1

下面通过引用module的方式引入testlib,在app的build.gradle的dependencies中加入
implementation project(path: ':testlib')
annotationProcessor project(path: ':testlib')
然后在app下编译报错

s2

按照错误提示在app的build.gradle的android下加入下面代码
lintOptions {
    abortOnError false
}
编译成功,在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成MainActivity$GA类
package com.bill.annotationpractice;

public class MainActivity$GA {
	public void print() {
		System.out.println("Hello");
	}
}

eg:AnnotationPractice-branch5

下篇文章 自定义注解-实现findViewById和onClick

猜你喜欢

转载自blog.csdn.net/y331271939/article/details/81773012