关于APT
APT(Annotation Processing Tool)是一种注解处理工具,它会对源文件进行扫描找出相应的Annotation并在注解处理器中进行操作,具体操作由注解处理器也就是用户自己去实现,比如可以生成一些新的文件或者其他文件等,最终会把新生成的文件和源文件一起进行编译。
APT工具常用的有2个,android-apt
和Gradle2.2以后的annotationProcessor
功能。
APT处理annotation的基本流程表示:
- 定义注解,比如@Route
- 自定义注解处理器,处理注解(如生成java文件等)
- 使用注解处理器
android-apt
一个Gradle插件帮助Android Studio处理annotation processors,Gradle2.2以后Gradle提供annotationProcessor的功能可以完全代替android-apt,android-apt官网上作者也说明了,不再维护,并且谷歌明确表示Gradle 3.0.0+ 不再支持 android-apt
插件,所以推荐使用annotationProcessor。
android-apt主要有2个目的:
1、允许在注解处理器编译的时候当做依赖,但是在打包apk或者当做类库的时候不会打到里面;
2、设置生成的资源路径以便能被Android studio正确访问到;
使用插件的时候如下配置gradle脚本
buildscript {
repositories {
mavenCentral()
}
dependencies {
// replace with the current version of the Android plugin
classpath 'com.android.tools.build:gradle:1.3.0'
// the latest version of the android-apt plugin
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
传递编译的参数
apt {
arguments {
resourcePackageName android.defaultConfig.applicationId
androidManifestFile variant.outputs[0]?.processResources?.manifestFile
}
}
由于android-apt已经过时了,并且annotationProcessor也正式被Google扶正,所以具体apt的使用不在进行演示,有兴趣的同学可以访问android-apt主页进行学习。
annotationProcessor
官方文档
annotationProcessor是Gradle2.2+内置的功能,不需要额外引入其他插件,可以向下面这样直接在gradle文件引入。
dependencies {
// Adds libraries defining annotations to only the compile classpath.
compileOnly 'com.google.dagger:dagger:version-number'
// Adds the annotation processor dependency to the annotation processor classpath.
annotationProcessor 'com.google.dagger:dagger-compiler:version-number'
}
这是引用第三方的注解处理器,我们实际开发中可以自定义注解处理器,下面我们自定义一个简单的注解处理器。
自定义注解处理器
自定义注解处理器的话需要用到2个第三方库AutoService和JavaPoet ,还有Java自带的AbstractProcessor。
-
AbstractProcessor
:Java内置注解处理器,注解处理器核心工作都在这个类进行。 -
AutoService
:Google开源用来自动注册我们自己的注解处理器。 -
JavaPoet
:Java代码生成器,方便我们生成Java文件;
我们按照上文说的APT处理annotation的基本流程来自定义。
1、定义注解,比如@Route
新建项目,然后新建一个Java module,叫annotationLib,里面定义我们自己的注解,关于注解的相关知识这里不再细说具体可以参考这里
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Route {
String value() ;
}
2、自定义注解处理器,处理注解(如生成java文件等)
再新建一个Java module,叫annotationCompiler,里面实现具体的注解处理器,配置gradle文件引入 AutoService
和 JavaPoet
,再依赖上我们之前定义的注解模块。
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.8.0'
implementation project(':annotationLib')
}
sourceCompatibility = "7"
targetCompatibility = "7"
新建一个注解处理器继承自AbstractProcessor:
@AutoService(Processor.class)
public class RouteProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new HashSet<String>();
annotataions.add(Route.class.getCanonicalName());
return annotataions;
}
public void loggerInfo(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, msg);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && !set.isEmpty()) {
loggerInfo("process start");
StringBuilder printInfo = new StringBuilder();
Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
try {
if (routeElements != null && routeElements.size() > 0) {
printInfo.append(routeElements.size() + "个文件加了@Route注解!");
}
} catch (Exception e) {
loggerInfo(e.getMessage());
}
//构建参数
ParameterSpec msg = ParameterSpec.builder(String.class, "msg")
.build();
//构建方法
MethodSpec method = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(msg)
.addStatement("$T.out.println($S+msg)", System.class, printInfo.toString())
.build();
//构建类
TypeSpec helloWorld = TypeSpec.classBuilder("InjectHelper")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(method)
.build();
//构建文件并指定生成文件目录
JavaFile javaFile = JavaFile.builder("com.wzh.annotation", helloWorld)
.build();
loggerInfo("process end");
try {
//把类、方法、参数等信息写入文件
javaFile.writeTo(filer);
} catch (IOException e) {
loggerInfo("process exception");
e.printStackTrace();
}
return true;
}
return false;
}
}
然后我们在我们app模块引用这个注解编译器及注解,如下:
dependencies {
...
annotationProcessor project(':annotationCompiler')
implementation project(':annotationLib')
...
}
我们clean一下项目,然后rebuild一下,会发现如下目录生成的文件:
app/build/generated/source/apt/debug/com/wzh/annotation/InjectHelper.java
import java.lang.String;
import java.lang.System;
public final class InjectHelper {
public static void inject(String msg) {
System.out.println("2个文件加了@Route注解!"+msg);
}
}
这就是我们生成的文件,很简单一个InjectHelper类,里面一个静态方法inject,打印出加@Route的文件个数。
3、使用注解处理器
@Route("main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectHelper.inject("调用生成类的方法");
}
}
@Route("test")
public class TestClass {
//empty class
}
这里我们演示很简单,在Activity上加了@Route的注解,并调用生成类的方法,然后在任意类都可以加注解,因为我们没做任何注解类的限制,运行程序输出:
System.out: 2个文件加了@Route注解!调用生成类的方法
Kotlin使用注解处理器
首先app模块引入注解处理器的时候需要引入kapt
插件,在app下的gradle配置如下:
apply plugin: 'kotlin-kapt'
....
dependencies {
...
//自定义注解处理器 module
kapt project(':annotationCompiler')
//自定义注解 module
implementation project(':annotationLib')
...
}
其他配置基本一样,文件生成的目录变化,apt
目录变为kapt
:
app/build/generated/source/kapt/debug/com/wzh/annotation/InjectHelper.java
给注解处理器传参数
在编译之前可以传递需要的参数给注解处理器,我们在app模块gradle传递module的名字给注解处理器:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//参数名 route_module_name,携带的数据就是当前module的名字
arguments = [route_module_name: project.getName()]
}
}
}
}
在注解处理器init方法里接受参数:
private String moduleName = null;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
...
moduleName = processingEnvironment.getOptions().get("route_module_name");
loggerInfo("moduleName = " + moduleName);
}
Rebuild项目的时候我们会在build控制台看到如下输出信息:
moduleName = app
APT的相关知识学习
自定义AbstractProcessor
的时候我们会重写以下的方法:
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
// 初始化操作
}
@Override
public Set<String> getSupportedAnnotationTypes() {
// 设置注解处理器需要处理的注解类型
}
@Override
public SourceVersion getSupportedSourceVersion() {
//指定java版本
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//注解处理的核心方法
}
下面重点介绍init
和process
方法
-
init(ProcessingEnvironment processingEnvironment)
方法
此方法会被注解处理工具调用,参数ProcessingEnvironment 提供了一些实用的工具类Elements、Types和Filer等,如下表所示。
工具方法 | 功能 |
---|---|
getElementUtils() | 返回实现Elements接口的对象,用于操作元素的工具类 |
getFiler() | 返回实现Filer接口的对象,用于创建文件、类和辅助文件 |
getMessager() | 返回实现Messager接口的对象,用于报告错误信息、警告提醒 |
getOptions() | 返回指定的参数选项,可在Gradle文件配置 |
getTypeUtils() | 返回实现Types接口的对象,用于操作类型的工具类 |
-
process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
方法
此方法里面是我们进行注解处理逻辑的地方。
参数1 Set<? extends TypeElement> set
:返回所有当前注解处理器需要处理的Annotation.
参数2 RoundEnvironment roundEnvironment
:表示当前或是之前的运行环境,可以通过该对象查找到注解。
从roundEnvironment我们可以获取到Element
被注解的元素信息。下面我们写个实例来打印一下看看。
package com.wzh.annotation;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE,ElementType.TYPE_PARAMETER})
public @interface Test {
String value();
}
这里定义的注解为了方便打印,支持注解到类、方法、变量、参数等。下面使用注解。
package com.wzh.annotation;
@Test("this is class TestClass")
public class TestClass<T> implements TestInterface{
@Test("this is local field name")
private String name = "my name is test";
@Test("this is local method sayHello")
private String sayHello(@Test("this is parameter msg") String msg){
String hello = "my name is hello";
return hello;
}
}
然后在注解处理器去打印元素信息。
private void parseTestAnnotation(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Test.class);
for (Element element : elements){ //遍历所有元素
if(element.getKind().equals(ElementKind.PACKAGE)){
LoggerInfo("element--------------PACKAGE-------------------------------");
} else if (element.getKind().equals(ElementKind.CLASS)){
//被注解的元素是类
TypeElement typeElement = (TypeElement) element;
LoggerInfo("element--------------CLASS-------------------------------");
//实现接口信息
LoggerInfo("element:Interfaces = "+typeElement.getInterfaces().toString());
//泛型参数
LoggerInfo("element:TypeParameters = "+typeElement.getTypeParameters().toString());
//element的父元素是包元素
PackageElement packageElement = (PackageElement) element.getEnclosingElement();
LoggerInfo("element:packageElement = "+packageElement.getQualifiedName());
} else if (element.getKind().equals(ElementKind.FIELD)){
//被注解的元素是全局变量
LoggerInfo("element--------------FIELD-------------------------------");
VariableElement variableElement = (VariableElement) element;
//获取变量类型
LoggerInfo("element:typeSimpleName = "+ types.asElement(variableElement.asType()).getSimpleName());
} else if (element.getKind().equals(ElementKind.PARAMETER)){
//被注解的元素是参数
LoggerInfo("element--------------PARAMETER-------------------------------");
} else if (element.getKind().equals(ElementKind.METHOD)){
//被注解的元素是方法
LoggerInfo("element--------------METHOD-------------------------------");
ExecutableElement executableElement = (ExecutableElement) element;
//获取方法的参数名
LoggerInfo("element:Parameters = "+executableElement.getTypeParameters().toString());
//获取方法的返回值类型
LoggerInfo("element:ReturnType = "+executableElement.getReturnType().toString());
}
//打印注解里面的值
LoggerInfo("element:value = "+ element.getAnnotation(Test.class).value());
//打印包名信息
LoggerInfo("element:packageName = "+ elementUtils.getPackageOf(element).getQualifiedName());
//被注解元素的名称
LoggerInfo("element:SimpleName = "+element.getSimpleName());
//被注解元素的类型(String/int/float...)
LoggerInfo("element:asType = "+element.asType().toString());
//被注解元素的种类(PACKAGE、CLASS、METHOD、PARAMETER等)
LoggerInfo("element:KindName = "+element.getKind().name());
//获取父元素的种类(局部变量的父元素是方法、方法及全局变量的父元素是类、类元素的父元素是包)
LoggerInfo("element:EnclosingElementKindName = "+element.getEnclosingElement().getKind().name());
//被注解元素的修饰 如:public static 等
LoggerInfo("element:Modifiers = "+element.getModifiers().toString());
}
}
注: >> element--------------CLASS-------------------------------
注: >> element:Interfaces = com.wzh.annotation.TestInterface
注: >> element:TypeParameters = T
注: >> element:packageElement = com.wzh.annotation
注: >> element:value = this is class TestClass
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = TestClass
注: >> element:asType = com.wzh.annotation.TestClass<T>
注: >> element:KindName = CLASS
注: >> element:EnclosingElementKindName = PACKAGE
注: >> element:Modifiers = [public]
注: >> element--------------FIELD-------------------------------
注: >> element:typeSimpleName = String
注: >> element:value = this is local field name
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = name
注: >> element:asType = java.lang.String
注: >> element:KindName = FIELD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------METHOD-------------------------------
注: >> element:Parameters =
注: >> element:ReturnType = java.lang.String
注: >> element:value = this is local method sayHello
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = sayHello
注: >> element:asType = (java.lang.String)java.lang.String
注: >> element:KindName = METHOD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------PARAMETER-------------------------------
注: >> element:value = this is parameter msg
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = msg
注: >> element:asType = java.lang.String
注: >> element:KindName = PARAMETER
注: >> element:EnclosingElementKindName = METHOD
注: >> element:Modifiers = []
由打印结果可以看到所有被注解的元素信息都被打印出来。
Element
Java文档关于Element介绍
Element代表一个程序元素,如包、类、方法、变量、参数、接口泛型等的接口,其有多种子类分别代表不同的程序元素,如:ExecutableElement 方法元素
, PackageElement 包元素
, TypeElement 类元素
, TypeParameterElement 形参元素
, VariableElement 变量及参数元素
等。之前的TestClass<T>可以对应成下图。
TypeMirror
TypeMirror
是一个接口,表示Java编程语言中的类型。这些类型包括基本类型、引用类型、数组类型、类型变量和null类型等等。Element的 asType()
返回TypeMirror类型的值,我们通过这个值得getKind()
方法获取元素的类型,这个类型有很多枚举类型如:CHAR
、ARRAY(数组)
、FLOAT
、EXECUTABLE(方法)
等。
总结
关于注解处理器具体使用还有很多东西,就不一一写出来了,具体可以参考JAVA API,不得不说注解处理器很强大,很多热门框架都使用了APT,如:butterknife、Arouter、Dagger2、EventBus等。所以学好注解处理器还是比较重要的,接下来我们实战一把,不看butterknife源码的情况下实现简单的功能。Android开发— APT之ButterKnife的简单功能实现
网友评论