
網(wǎng)上有很多 APT 相關(guān)教程,最近開始學(xué)這個(gè),發(fā)現(xiàn)有一些內(nèi)容已經(jīng)過時(shí)了,在使用過程中也發(fā)現(xiàn)了一些坑,總結(jié)一下,形成這篇教程。
本文開發(fā)環(huán)境:2019年5月初最新版本的 Android Studio 3.4、Android Plugin 3.4.0、Gradle 5.1.1。
本教程需要讀者了解注解 Annotation 的基本知識(shí),不涉及 Annotation 運(yùn)行時(shí)反射的用法,專注于自定義 APT 的流程和步驟,以及使用新版 AS 和 gradle 的注意事項(xiàng)。
簡(jiǎn)介
APT,Annotation Processing Tool,注解處理工具,是 JDK 提供的一個(gè)工具。注意是 Java 語言支持的,不是安卓特有的東西,這點(diǎn)對(duì)于理解 APT 有一些作用。早期的 JDK 提供了一個(gè)單獨(dú)的 apt 程序,后來被整合到 javac 中了。它最常見的用法就是根據(jù)注解自動(dòng)生成源代碼,很多流行的庫都使用了注解處理器來生成代碼,比如 ButterKnife 會(huì)生成資源與變量綁定的代碼,讓開發(fā)者不用手寫繁瑣重復(fù)的 findViewById。
原理
那么 javac 是怎么使用 APT 生成代碼的呢?javac 并不知道你想怎么生成代碼,需要你按照 javac 提供的規(guī)則和接口來自定義 Annotation Processor。注意這是 Java 語言定義的規(guī)則。
接口
javax.annotation.processing.AbstractProcessor,實(shí)現(xiàn)這個(gè)抽象類,在 process() 方法中自定義生成代碼的細(xì)節(jié)??梢苑Q它為注解處理器 Annotation Processor。只有這一個(gè)類型作為接口,當(dāng)然類中還有一些其他方法用來設(shè)置 Annotation Processor 的屬性。
規(guī)則
- 一個(gè) Annotation Processor 想要參與到 javac 的編譯過程中,就要被編譯打包成一個(gè) jar 文件。
- 這個(gè) jar 文件要放置在編譯期的 classpath 中,javac 會(huì)自動(dòng)查找 classpath 中所有的 Annotation Processor,自動(dòng)完成注解處理。然而新版的 gradle 5 不再將 Annotation Processor 放在編譯期的 classpath 中,導(dǎo)致還需要額外處理,這個(gè)是 gradle 的行為,處理方法見下文。
- 這個(gè) jar 文件中包含一個(gè)
META-INF/service/javax.annotation.processing.Processor文件,文件內(nèi)容是文本,每行一個(gè) Annotation Processor 的完整類名稱。
安卓和 gradle
以上是 Java 的基礎(chǔ)規(guī)則,到了 gradle 中就要按照 Java Plugin 的語法和規(guī)則來配置。gradle 一直致力于提高編譯速度,在新版的 gradle 5.+ 中,為了推行更快速的增量編譯,關(guān)閉了一個(gè)默認(rèn)功能,導(dǎo)致由 gradle 4.+ 升級(jí)上來的項(xiàng)目有可能構(gòu)建失敗,這其中的彎彎繞繞和坑坑洼洼在下面的步驟中詳細(xì)講解。
步驟
1. 項(xiàng)目架構(gòu)

分 3 個(gè)模塊:
- annotation 模塊:用來定義注解。
- compiler 模塊:用來定義 Annotation Processor。
- app 模塊:使用注解的應(yīng)用模塊。
為什么要分這么多模塊?其中 app 模塊是用來測(cè)試的,測(cè)試新定義的 Annotation Processor 能否成功運(yùn)行。annotation + compiler 如果寫得糙一點(diǎn)可以合并在一起,例如谷歌的 auto service。但兩者的目的并不一樣,annotation 是專門定義注解的,而 compiler 是處理注解的。最重要的是,annotation(RetentionPolicy.CLASS,RetentionPolicy.RUNTIME)是需要被編譯到 app 項(xiàng)目的 class 文件中的,而 compiler 沒有必要進(jìn)入 app 中。
2. annotation 模塊
該模塊是定義注解用的,可以包含多個(gè)注解,例如 ButterKnife 就有二十多個(gè)注解定義。而且只有注解的定義,沒有其他任何代碼。這樣做的原因主要是在架構(gòu)上能單獨(dú)隔離一個(gè)完整內(nèi)聚的功能,可以被其他模塊引用,比如 app 模塊必須引用,compiler 模塊可以引用。
創(chuàng)建模塊
annotation 模塊中只有注解定義,可以直接定義為 java library,而不用定義為 android library,定義為安卓庫反而會(huì)限制它被其他 java library 引用。

build.gradle
apply plugin: 'java-library'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
然后就可以在這個(gè)模塊中添加注解定義了。
注解簡(jiǎn)介
定義注解使用 @interface 關(guān)鍵字,然后使用元注解 @Target 和 @Retention 定義注解的修飾目標(biāo)和保留策略:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface NiceField {}
-
@Target指定和約束注解能修飾的代碼元素,可以是類、字段、方法、參數(shù)等等。 -
@Retention指定保留策略,指的是被修飾的代碼,在編譯后是否仍保留注解的策略,有三種:-
RetentionPolicy.SOURCE:不做任何保留,編譯之后就拋棄。 -
RetentionPolicy.CLASS:保留在 class 文件中,但運(yùn)行時(shí)無法使用。 -
RetentionPolicy.RUNTIME:保留在 class 文件中,運(yùn)行時(shí)可以通過反射使用。
-
當(dāng)注解被 Annotation Processor 處理的時(shí)候,其實(shí)這三種策略的注解都是可以處理的。比如谷歌的 auto service 中的 @AutoService 就是 RetentionPolicy.SOURCE 類型的,用完即拋。如果還有其他運(yùn)行時(shí)處理的需求,可以使用 RetentionPolicy.RUNTIME。
3. compiler 模塊
該模塊編譯 Annotation Processor 代碼,并將其打包成 jar 文件供其他模塊使用,一般都起名為 compiler 或 processor。
創(chuàng)建模塊
上文說過只要將 jar 放置在了 classpath 中,javac 就會(huì)自動(dòng)查找并執(zhí)行處理代碼。因此只需要?jiǎng)?chuàng)建一個(gè) java library 模塊:

build.gradle
apply plugin: 'java-library'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
implementation 'com.squareup:javapoet:1.10.0' // 使用 javapoet 生成 .java 文件
implementation project(':annotation') // 依賴 annotation 模塊方便引用其中的注解
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
}
最后兩行的依賴是谷歌的 auto service。為啥兩行后面是一樣的?
谷歌 auto service 和 META-INF
谷歌的 auto service 也是一種 Annotation Processor,它能自動(dòng)生成 META-INF 目錄以及相關(guān)文件,避免手工創(chuàng)建該文件,手工創(chuàng)建有可能失誤(我就寫錯(cuò)過路徑)。使用 auto service 中的 @AutoService(Processor.class) 注解修飾 Annotation Processor 類就可以在編譯過程中自動(dòng)生成文件。
可見 auto service 是給 Annotation Processor 服務(wù)的 Annotation Processor,是不是很有趣??赡苡腥艘獑?auto service 的 META-INF 目錄和文件怎么辦?不是還可以手工寫嘛。
手工怎么寫這個(gè) META-INF? 在 jar 包中路徑是
META-INF/service/javax.annotation.processing.Processor,見下圖:

但在項(xiàng)目中應(yīng)該放在哪里呢?見下圖:

手工生成這個(gè) META-INF 目錄和文件就不用引入 auto service 依賴了。
如果引入的話,還要注意有兩個(gè)配置 compileOnly 和 annotationProcessor。
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
這兩個(gè)重復(fù)寫的原因就是 auto service 并沒有將 annotation 和 processor 分割成兩個(gè)項(xiàng)目,而是混到了一起。
-
compileOnly所需的只是@AutoService這個(gè)注解。表示只參與編譯過程并不打包到最終產(chǎn)物 jar 文件中,而注解@AutoService的 RetentionPolicy 就是 SOURCE,不會(huì)編譯生成到 class 文件中。即便使用compile依賴,最終生成的 jar 包中只會(huì)多了 auto service 提供的注解,其他 class 文件部分不受影響,因此可以使用compileOnly只參與編譯過程。 -
annotationProcessor這個(gè)是新版 gradle 提供的 java plugin 內(nèi)置的配置項(xiàng),代替了早期第三方提供的android-apt插件。而且,敲重點(diǎn),在 gradle 5.+ 中將 Annotation Processor 從編譯期 classpath 中去除了,javac 也就無法發(fā)現(xiàn) Annotation Processor。此處如果按照 gradle 4.+ 的寫法,只寫一個(gè)compileOnly是無法使用 auto service 的 Annotation Processor 的。必須要使用annotationProcessor來配置 Annotation Processor 使其生效。
定制化 Processor
兩個(gè)部分:
- 定義文件生成規(guī)則
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 因?yàn)檫\(yùn)行在 javac 執(zhí)行過程中,打印必須使用 messager,而不能使用 System.out
messager.printMessage(Diagnostic.Kind.NOTE, "processing");
// 此處正經(jīng)工具應(yīng)該使用參數(shù) set 和 roundEnvironment 根據(jù)注解的具體使用情況來生成代碼
// 本文主要講步驟和配置,不涉及這個(gè)部分。僅生成了一個(gè)獨(dú)立的 java 文件。
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.study.app", helloWorld)
.build();
try {
// 最后要將內(nèi)容寫入到 java 文件中,這里必須使用 processingEnv 中獲取的 Filer 對(duì)象
// 它會(huì)自動(dòng)處理路徑問題,我們只需要定義好包名類名和文件內(nèi)容即可。
Filer filer = processingEnv.getFiler();
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
// 返回值表示處理了 set 參數(shù)中包含的所有注解,不會(huì)再將這些注解移交給編譯流程中的
// 其他 Annotation Processor。一般都不會(huì)有多個(gè) Annotation Processor,一般都寫 true。
return true;
}
- 配置 Processor。有兩種配置方法:可以用注解的方式也可以重寫
AbstractProcessor的某些方法。如果兩種方式都定義,則會(huì)使用方法的版本,但從工程的角度應(yīng)該只用一種方法來設(shè)置,以免混淆。這些注解設(shè)置為了 RetentionPolicy.RUNTIME 類型,如果不重寫相關(guān)方法,AbstractProcessor中方法的默認(rèn)實(shí)現(xiàn)則會(huì)使用反射來獲取注解中設(shè)置的值。
// 注解方式
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.study.annotation.NiceField")
public class MyProcessor extends AbstractProcessor {
// 重寫方法方式
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(NiceField.class.getCanonicalName());
}
}
- SupportedSourceVersion 指定最低支持的源代碼版本,這是指 javac 的版本,對(duì)安卓來說只有 7 和 8 的區(qū)別,直接使用當(dāng)前編譯環(huán)境最高支持版本即可:
SourceVersion.latestSupported(); - SupportedAnnotationTypes 指定該 Annotation Processor 可以處理哪些注解,這里要返回一個(gè)字符串的集合,字符串內(nèi)容是這些注解的完整類路徑,即
class.getCanonicalName()。
4. app 模塊
該模塊用來測(cè)試和驗(yàn)證 Annotation Processor,是一個(gè)簡(jiǎn)單的 android application 模塊。
創(chuàng)建模塊
就不貼圖了,默認(rèn)一個(gè) android 項(xiàng)目就會(huì)有一個(gè) app 模塊,看下面的 build.gradle:
// 前半部分都是自動(dòng)生成的不用細(xì)看
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.study.app"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
// 上面部分都是自動(dòng)生成的不用細(xì)看
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// 使用 annotationProcessor 來指定作為 Annotation Processor 的模塊
annotationProcessor project(':compiler')
// 引入自定義的注解
implementation project(':annotation')
}
使用注解
package com.ajeyone.study.aptda;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.ajeyone.study.annotation.NiceField;
public class MainActivity extends AppCompatActivity {
@NiceField // 在這里
int currentValue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
currentValue = currentValue + 1;
}
}
構(gòu)建后生成了 HelloWorld.java:

總結(jié)
本文介紹了 APT 的原理以及自定義 APT 的流程和步驟。在研究過程中也發(fā)現(xiàn)了一些坑:
- gradle 腳本中要使用
annotationProcessor來指定 Annotation Processor 庫。 - 谷歌 auto service 沒有分離 annotation 和 processor,在新版的 gradle 腳本中需要用
annotationProcessor將其指定為 Annotation Processor 才會(huì)起作用。
關(guān)于 APT 中另一個(gè)塊重要的內(nèi)容,如何通過注解獲取源代碼的信息,并生成目標(biāo)源文件,建議閱讀一些流行開源項(xiàng)目的源代碼,比如 ButterKnife,Dagger2 等等。
另外還有一個(gè)比較新的東西是 incremental annotation processor,這個(gè)是 gradle 4.7 就搞出來的加快編譯速度的功能,跟 java 本身沒有關(guān)系,但是要引入 gradle 提供的一些工具來修改 Annotation Processor,感興趣的可以參考一下官網(wǎng)的資料以及一些開源項(xiàng)目的實(shí)現(xiàn),比如 Dagger2。https://docs.gradle.org/4.7/userguide/java_plugin.html#sec:incremental_annotation_processing