越来越多第三方库使用 apt 技术, 如 DBflow https://github.com/Raizlabs/DBFlow ,Dagger2 https://github.com/google/dagger ,ButterKnife https://github.com/JakeWharton/butterknife , https://github.com/joyrun/ActivityRouter , https://github.com/joyrun/AptPreferences . 在编译时根据 Annotation 生成了相关的代码, 非常高大上但是也非常简单的技术, 可以给开发带来了很大的便利.
Annotation
如果想学习 APT, 那么就必须先了解 Annotation 的基础, 这里附加我另外一篇文章的地址:
Java Annotation 基础教程 http://www.taoweiji.cn/2016/07/18/java-annotation
APT
APT(Annotation Processing Tool) 是一种处理注释的工具, 它对源代码文件进行检测找出其中的 Annotation, 使用 Annotation 进行额外的处理.
Annotation 处理器在处理 Annotation 时可以根据源文件中的 Annotation 生成额外的源文件和其它的文件 (文件具体内容由 Annotation 处理器的编写者决定),APT 还会编译生成的源文件和原来的源文件, 将它们一起生成 class 文件.
创建 Annotation Module
首先, 我们需要新建一个名称为 annotation 的 Java Library, 主要放置一些项目中需要使用到的 Annotation 和关联代码. 这里简单自定义了一个注解:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.CLASS)
- public @interface Test { }
配置 build.gradle, 主要是规定 jdk 版本
- apply plugin: 'java'
- sourceCompatibility = 1.7
- targetCompatibility = 1.7
- dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- }
创建 Compiler Module
创建一个名为 compiler 的 Java Library, 这个类将会写代码生成的相关代码. 核心就是在这里.
配置 build.gradle
- apply plugin: 'java'
- sourceCompatibility = 1.7
- targetCompatibility = 1.7
- dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.google.auto.service:auto-service:1.0-rc2'
- compile 'com.squareup:javapoet:1.7.0'
- compile project(':annotation')
- }
定义编译的 jdk 版本为 1.7, 这个很重要, 不写会报错.
AutoService 主要的作用是注解 processor 类, 并对其生成 META-INF 的配置信息.
JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码.
依赖上面创建的 annotation Module.
定义 Processor 类
生成代码相关的逻辑就放在这里.
- @AutoService(Processor.class)
- public class TestProcessor extends AbstractProcessor {
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- return Collections.singleton(Test.class.getCanonicalName());
- }
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- return false;
- }
- }
生成第一个类
我们接下来要生成下面这个 HelloWorld 的代码:
- package com.example.helloworld;
- public final class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello, JavaPoet!");
- }
- }
修改上述 TestProcessor 的 process 方法
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- MethodSpec main = MethodSpec.methodBuilder("main")
- .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
- .returns(void.class)
- .addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
- .build();
- TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addMethod(main)
- .build();
- JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
- .build();
- try {
- javaFile.writeTo(processingEnv.getFiler());
- } catch (IOException e) {
- e.printStackTrace();
- }
- return false;
- }
在 app 中使用
配置项目根目录的 build.gradle
- dependencies {
- classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- }
配置 app 的 build.gradle
- apply plugin: 'com.android.application'
- apply plugin: 'com.neenbedankt.android-apt'
- //...
- dependencies {
- //..
- compile project(':annotation')
- apt project(':compiler')
- }
编译使用
在随意一个类添加 @Test 注解 mailto:在随意一个类添加@Test注解
- @Test
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- }
点击 Android Studio 的 ReBuild Project, 可以在在 app 的
build/generated/source/apt
目录下, 即可看到生成的代码.
基于注解的 View 注入: DIActivity
到目前我们还没有使用注解, 上面的 @Test 也没有实际用上 mailto:上面的@Test也没有实际用上 , 下面我们做一些更加实际的代码生成. 实现基于注解的 View, 代替项目中的 findByView. 这里仅仅是学习怎么用 APT, 如果真的想用 DI 框架, 推荐使用 ButterKnife, 功能全面.
第一步, 在 annotation module 创建 @DIActivity mailto:module创建@DIActivity ,@DIView 注解.
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.CLASS)
- public @interface DIActivity {
- }
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DIView {
- int value() default 0;
- }
创建 DIProcessor 方法
- @AutoService(Processor.class)
- public class DIProcessor extends AbstractProcessor {
- private Elements elementUtils;
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- // 规定需要处理的注解
- return Collections.singleton(DIActivity.class.getCanonicalName());
- }
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- System.out.println("DIProcessor");
- Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class);
- for (Element element : elements) {
- // 判断是否 Class
- TypeElement typeElement = (TypeElement) element;
- List<? extends Element> members = elementUtils.getAllMembers(typeElement);
- MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView")
- .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
- .returns(TypeName.VOID)
- .addParameter(ClassName.get(typeElement.asType()), "activity");
- for (Element item : members) {
- DIView diView = item.getAnnotation(DIView.class);
- if (diView == null){
- continue;
- }
- bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(R.id.text)",item.getSimpleName(),ClassName.get(item.asType()).toString()));
- }
- TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
- .superclass(TypeName.get(typeElement.asType()))
- .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- .addMethod(bindViewMethodSpecBuilder.build())
- .build();
- JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
- try {
- javaFile.writeTo(processingEnv.getFiler());
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return true;
- }
- private String getPackageName(TypeElement type) {
- return elementUtils.getPackageOf(type).getQualifiedName().toString();
- }
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- elementUtils = processingEnv.getElementUtils();
- }
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.RELEASE_7;
- }
- }
使用 DIActivity
- @DIActivity
- public class MainActivity extends Activity {
- @DIView(R.id.text)
- TextView textView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- DIMainActivity.bindView(this);
- textView.setText("Hello World!");
- }
- }
实际上就是通过 apt 生成以下代码
- public final class DIMainActivity extends MainActivity {
- public static void bindView(MainActivity activity) {
- activity.textView = (android.widget.TextView) activity.findViewById(R.id.text);
- }
- }
常用方法
常用 Element 子类
TypeElement: 类
ExecutableElement: 成员方法
VariableElement: 成员变量
通过包名和类名获取 TypeName
TypeName targetClassName = ClassName.get("PackageName", "ClassName");
通过 Element 获取 TypeName
TypeName type = TypeName.get(element.asType());
获取 TypeElement 的包名
String packageName = processingEnv.getElementUtils().getPackageOf(type).getQualifiedName().toString();
获取 TypeElement 的所有成员变量和成员方法
List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(typeElement);
总结
来源: https://www.cnblogs.com/taoweiji/p/5744975.html