APT技术

官方文档:Getting Started with the Annotation Processing Tool (apt)

Annotation

如果想学习APT,那么就必须先了解Annotation的基础,这里附加我另外一篇文章的地址:
Annotation - qq_20198405的博客 - 博客频道 - youkuaiyun.com

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 AptAnnotation {
}

配置build.gradle

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

创建Compiler Module

新建一个名为compiler的Java Library,这个类将会写代码生成的相关代码。核心就是在这里。

配置build.gradle

apply plugin: 'java'
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
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')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
  1. AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
  2. JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
  3. tasks.withType是设置编码格式,避免AptProcesser 中的中文注释报错
  4. 依赖上面创建的annotation Module。

定义Processor类

生成代码相关的逻辑就放在这里。

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

        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(AptAnnotation.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!");
      }
    }

修改上述AptProcesser 的process方法

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        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')
}

编译使用

@AptAnnotation
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/debug 目录下,即可看到生成的代码。

基于注解的View注入:DIActivity

到目前我们还没有使用注解,上面的@AptAnnotation 也没有实际用上,下面我们做一些更加实际的代码生成。实现基于注解的View,代替项目中的findByView 。这里仅仅是学习怎么用APT,如果真的想用DI框架,推荐使用ButterKnife,功能全面。

第一步,在annotation 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(%s)",item.getSimpleName(),ClassName.get(item.asType()).toString(),diView.value()));
            }
            TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .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

@AptAnnotation
@DIActivity
public class MainActivity extends AppCompatActivity {
    @DIView(R.id.text1)
    TextView textView1;
    @DIView(R.id.text2)
    TextView textView2;
    @DIView(R.id.text3)
    TextView textView3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DIMainActivity.bindView(this);
        textView1.setText("text1");
        textView2.setText("text2");
        textView3.setText("text3");

    }
}

实际上就是通过apt生成以下代码:

public final class DIMainActivity {
  public static void bindView(MainActivity activity) {
    activity.textView1 = (android.widget.TextView) activity.findViewById(2131427413);
    activity.textView2 = (android.widget.TextView) activity.findViewById(2131427414);
    activity.textView3 = (android.widget.TextView) activity.findViewById(2131427415);
  }
}

常用方法

常用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);

总结:

推荐阅读dagger2、dbflow、ButterKnife等基于apt的开源项目代码。JavaPoet 也有很多例子可以学习。

源码传送
参考:
Android APT(编译时代码生成)最佳实践 - 推酷
例子:
代码传送(稍加注释)
Android 利用 APT 技术在编译期生成代码 - hb707934728的博客 - 博客频道 - youkuaiyun.com

相关文章:
android-apt 翻译 - 简书
原文hvisser / android-apt — Bitbucket
Annotation-Processing-Tool详解 | qiushao
http://qiushao.net/2015/07/07/Annotation-Processing-Tool%E8%AF%A6%E8%A7%A3/
AndroidSutdio 编译时自动生成源代码 | Septenary
http://www.septenary.cn/2015/12/19/AndroidSutdio-%E7%BC%96%E8%AF%91%E6%97%B6%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E6%BA%90%E4%BB%A3%E7%A0%81/


Android 打造编译时注解解析框架 这只是一个开始 - Hongyang - 博客频道 - youkuaiyun.com
http://blog.youkuaiyun.com/lmj623565791/article/details/43452969
Android 如何编写基于编译时注解的项目 - Hongyang - 博客频道 - youkuaiyun.com
http://blog.youkuaiyun.com/lmj623565791/article/details/51931859

bug解决:
Android Studio出现Error:No service of type Factor… - 简书
http://www.jianshu.com/p/c4f4894ad215
Android Studio Error—Gradle: 错误:编码 GBK 的不可映射字符的 - 黑暗领域 - 博客频道 - youkuaiyun.com
http://blog.youkuaiyun.com/sljjyy/article/details/11976099

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值