關(guān)于APT
APT(Annotation Processing Tool)是一種注解處理工具脖阵,它會(huì)對(duì)源文件進(jìn)行掃描找出相應(yīng)的Annotation并在注解處理器中進(jìn)行操作黄锤,具體操作由注解處理器也就是用戶自己去實(shí)現(xiàn),比如可以生成一些新的文件或者其他文件等饥侵,最終會(huì)把新生成的文件和源文件一起進(jìn)行編譯。
APT工具常用的有2個(gè),android-apt
和Gradle2.2以后的annotationProcessor
功能萄凤。
APT處理annotation的基本流程表示:
- 定義注解,比如@Route
- 自定義注解處理器搪哪,處理注解(如生成java文件等)
- 使用注解處理器
android-apt
一個(gè)Gradle插件幫助Android Studio處理annotation processors靡努,Gradle2.2以后Gradle提供annotationProcessor的功能可以完全代替android-apt,android-apt官網(wǎng)上作者也說(shuō)明了晓折,不再維護(hù)惑朦,并且谷歌明確表示Gradle 3.0.0+ 不再支持 android-apt
插件,所以推薦使用annotationProcessor漓概。
android-apt主要有2個(gè)目的:
1漾月、允許在注解處理器編譯的時(shí)候當(dāng)做依賴,但是在打包apk或者當(dāng)做類庫(kù)的時(shí)候不會(huì)打到里面垛耳;
2栅屏、設(shè)置生成的資源路徑以便能被Android studio正確訪問(wèn)到;
使用插件的時(shí)候如下配置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'
傳遞編譯的參數(shù)
apt {
arguments {
resourcePackageName android.defaultConfig.applicationId
androidManifestFile variant.outputs[0]?.processResources?.manifestFile
}
}
由于android-apt已經(jīng)過(guò)時(shí)了堂鲜,并且annotationProcessor也正式被Google扶正栈雳,所以具體apt的使用不在進(jìn)行演示,有興趣的同學(xué)可以訪問(wèn)android-apt主頁(yè)進(jìn)行學(xué)習(xí)缔莲。
annotationProcessor
官方文檔
annotationProcessor是Gradle2.2+內(nèi)置的功能哥纫,不需要額外引入其他插件,可以向下面這樣直接在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'
}
這是引用第三方的注解處理器蛀骇,我們實(shí)際開(kāi)發(fā)中可以自定義注解處理器,下面我們自定義一個(gè)簡(jiǎn)單的注解處理器读拆。
自定義注解處理器
自定義注解處理器的話需要用到2個(gè)第三方庫(kù)AutoService和JavaPoet 擅憔,還有Java自帶的AbstractProcessor。
-
AbstractProcessor
:Java內(nèi)置注解處理器檐晕,注解處理器核心工作都在這個(gè)類進(jìn)行暑诸。 -
AutoService
:Google開(kāi)源用來(lái)自動(dòng)注冊(cè)我們自己的注解處理器。 -
JavaPoet
:Java代碼生成器辟灰,方便我們生成Java文件个榕;
我們按照上文說(shuō)的APT處理annotation的基本流程來(lái)自定義。
1芥喇、定義注解西采,比如@Route
新建項(xiàng)目,然后新建一個(gè)Java module继控,叫annotationLib械馆,里面定義我們自己的注解胖眷,關(guān)于注解的相關(guān)知識(shí)這里不再細(xì)說(shuō)具體可以參考這里
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Route {
String value() ;
}
2、自定義注解處理器霹崎,處理注解(如生成java文件等)
再新建一個(gè)Java module瘦材,叫annotationCompiler,里面實(shí)現(xiàn)具體的注解處理器仿畸,配置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"
新建一個(gè)注解處理器繼承自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() + "個(gè)文件加了@Route注解错沽!");
}
} catch (Exception e) {
loggerInfo(e.getMessage());
}
//構(gòu)建參數(shù)
ParameterSpec msg = ParameterSpec.builder(String.class, "msg")
.build();
//構(gòu)建方法
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();
//構(gòu)建類
TypeSpec helloWorld = TypeSpec.classBuilder("InjectHelper")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(method)
.build();
//構(gòu)建文件并指定生成文件目錄
JavaFile javaFile = JavaFile.builder("com.wzh.annotation", helloWorld)
.build();
loggerInfo("process end");
try {
//把類簿晓、方法、參數(shù)等信息寫(xiě)入文件
javaFile.writeTo(filer);
} catch (IOException e) {
loggerInfo("process exception");
e.printStackTrace();
}
return true;
}
return false;
}
}
然后我們?cè)谖覀僡pp模塊引用這個(gè)注解編譯器及注解千埃,如下:
dependencies {
...
annotationProcessor project(':annotationCompiler')
implementation project(':annotationLib')
...
}
我們clean一下項(xiàng)目憔儿,然后rebuild一下,會(huì)發(fā)現(xiàn)如下目錄生成的文件:
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個(gè)文件加了@Route注解放可!"+msg);
}
}
這就是我們生成的文件谒臼,很簡(jiǎn)單一個(gè)InjectHelper類,里面一個(gè)靜態(tài)方法inject耀里,打印出加@Route的文件個(gè)數(shù)蜈缤。
3、使用注解處理器
@Route("main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectHelper.inject("調(diào)用生成類的方法");
}
}
@Route("test")
public class TestClass {
//empty class
}
這里我們演示很簡(jiǎn)單冯挎,在Activity上加了@Route的注解底哥,并調(diào)用生成類的方法,然后在任意類都可以加注解房官,因?yàn)槲覀儧](méi)做任何注解類的限制趾徽,運(yùn)行程序輸出:
System.out: 2個(gè)文件加了@Route注解!調(diào)用生成類的方法
Kotlin使用注解處理器
首先app模塊引入注解處理器的時(shí)候需要引入kapt
插件翰守,在app下的gradle配置如下:
apply plugin: 'kotlin-kapt'
....
dependencies {
...
//自定義注解處理器 module
kapt project(':annotationCompiler')
//自定義注解 module
implementation project(':annotationLib')
...
}
其他配置基本一樣孵奶,文件生成的目錄變化,apt
目錄變?yōu)?code>kapt:
app/build/generated/source/kapt/debug/com/wzh/annotation/InjectHelper.java
給注解處理器傳參數(shù)
在編譯之前可以傳遞需要的參數(shù)給注解處理器蜡峰,我們?cè)赼pp模塊gradle傳遞module的名字給注解處理器:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//參數(shù)名 route_module_name了袁,攜帶的數(shù)據(jù)就是當(dāng)前module的名字
arguments = [route_module_name: project.getName()]
}
}
}
}
在注解處理器init方法里接受參數(shù):
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項(xiàng)目的時(shí)候我們會(huì)在build控制臺(tái)看到如下輸出信息:
moduleName = app
APT的相關(guān)知識(shí)學(xué)習(xí)
自定義AbstractProcessor
的時(shí)候我們會(huì)重寫(xiě)以下的方法:
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
// 初始化操作
}
@Override
public Set<String> getSupportedAnnotationTypes() {
// 設(shè)置注解處理器需要處理的注解類型
}
@Override
public SourceVersion getSupportedSourceVersion() {
//指定java版本
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//注解處理的核心方法
}
下面重點(diǎn)介紹init
和process
方法
-
init(ProcessingEnvironment processingEnvironment)
方法
此方法會(huì)被注解處理工具調(diào)用,參數(shù)ProcessingEnvironment 提供了一些實(shí)用的工具類Elements事示、Types和Filer等早像,如下表所示僻肖。
工具方法 | 功能 |
---|---|
getElementUtils() | 返回實(shí)現(xiàn)Elements接口的對(duì)象肖爵,用于操作元素的工具類 |
getFiler() | 返回實(shí)現(xiàn)Filer接口的對(duì)象,用于創(chuàng)建文件臀脏、類和輔助文件 |
getMessager() | 返回實(shí)現(xiàn)Messager接口的對(duì)象劝堪,用于報(bào)告錯(cuò)誤信息冀自、警告提醒 |
getOptions() | 返回指定的參數(shù)選項(xiàng),可在Gradle文件配置 |
getTypeUtils() | 返回實(shí)現(xiàn)Types接口的對(duì)象秒啦,用于操作類型的工具類 |
-
process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
方法
此方法里面是我們進(jìn)行注解處理邏輯的地方熬粗。
參數(shù)1 Set<? extends TypeElement> set
:返回所有當(dāng)前注解處理器需要處理的Annotation.
參數(shù)2 RoundEnvironment roundEnvironment
:表示當(dāng)前或是之前的運(yùn)行環(huán)境,可以通過(guò)該對(duì)象查找到注解余境。
從roundEnvironment我們可以獲取到Element
被注解的元素信息驻呐。下面我們寫(xiě)個(gè)實(shí)例來(lái)打印一下看看。
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();
}
這里定義的注解為了方便打印芳来,支持注解到類含末、方法、變量即舌、參數(shù)等佣盒。下面使用注解。
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-------------------------------");
//實(shí)現(xiàn)接口信息
LoggerInfo("element:Interfaces = "+typeElement.getInterfaces().toString());
//泛型參數(shù)
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)){
//被注解的元素是參數(shù)
LoggerInfo("element--------------PARAMETER-------------------------------");
} else if (element.getKind().equals(ElementKind.METHOD)){
//被注解的元素是方法
LoggerInfo("element--------------METHOD-------------------------------");
ExecutableElement executableElement = (ExecutableElement) element;
//獲取方法的參數(shù)名
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 = []
由打印結(jié)果可以看到所有被注解的元素信息都被打印出來(lái)耀石。
Element
Java文檔關(guān)于Element介紹
Element代表一個(gè)程序元素笼沥,如包、類娶牌、方法奔浅、變量、參數(shù)诗良、接口泛型等的接口汹桦,其有多種子類分別代表不同的程序元素,如:ExecutableElement 方法元素
, PackageElement 包元素
, TypeElement 類元素
, TypeParameterElement 形參元素
, VariableElement 變量及參數(shù)元素
等鉴裹。之前的TestClass<T>可以對(duì)應(yīng)成下圖舞骆。
TypeMirror
TypeMirror
是一個(gè)接口,表示Java編程語(yǔ)言中的類型径荔。這些類型包括基本類型督禽、引用類型、數(shù)組類型总处、類型變量和null類型等等狈惫。Element的 asType()
返回TypeMirror類型的值,我們通過(guò)這個(gè)值得getKind()
方法獲取元素的類型鹦马,這個(gè)類型有很多枚舉類型如:CHAR
胧谈、ARRAY(數(shù)組)
忆肾、FLOAT
、EXECUTABLE(方法)
等菱肖。
總結(jié)
關(guān)于注解處理器具體使用還有很多東西客冈,就不一一寫(xiě)出來(lái)了,具體可以參考JAVA API稳强,不得不說(shuō)注解處理器很強(qiáng)大场仲,很多熱門框架都使用了APT,如:butterknife退疫、Arouter燎窘、Dagger2、EventBus等蹄咖。所以學(xué)好注解處理器還是比較重要的褐健,接下來(lái)我們實(shí)戰(zhàn)一把,不看butterknife源碼的情況下實(shí)現(xiàn)簡(jiǎn)單的功能澜汤。Android開(kāi)發(fā)— APT之ButterKnife的簡(jiǎn)單功能實(shí)現(xiàn)