1、概念
APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具,確切的說(shuō)它是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解。注解處理器以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成.java文件作為輸出。
簡(jiǎn)單來(lái)說(shuō)就是在編譯期,通過(guò)注解生成.java文件。
在Java注解中提到根據(jù)@Retention可以將注解分為三種,Java注解舉的栗子都是運(yùn)行期注解,通過(guò)反射應(yīng)用,這必然帶來(lái)性能問(wèn)題,那么有沒(méi)有更好的實(shí)現(xiàn)方式呢?既可以實(shí)現(xiàn)注入,還能保證性能無(wú)損耗呢?
當(dāng)然有,就是編譯時(shí)注解,一般這類注解會(huì)在編譯的時(shí)候,根據(jù)注解標(biāo)識(shí),動(dòng)態(tài)生成一些類或者生成一些xml都可以,在運(yùn)行時(shí)期,這類注解是沒(méi)有的,會(huì)依靠動(dòng)態(tài)生成的類做一些操作,因?yàn)闆](méi)有反射,效率和直接調(diào)用方法沒(méi)什么區(qū)別。
2、作用
使用APT的優(yōu)點(diǎn)就是方便、簡(jiǎn)單,可以少些很多重復(fù)的代碼。
用過(guò)ButterKnife、ARouter、EventBus等注解框架的同學(xué)就能感受到,利用這些框架可以少些很多代碼,只要寫一些注解就可以了。
其實(shí),他們不過(guò)是通過(guò)注解,生成了一些代碼。
Java API 已經(jīng)提供了掃描源碼并解析注解的框架,開(kāi)發(fā)者可以通過(guò)繼承 AbstractProcessor 類來(lái)實(shí)現(xiàn)自己的注解解析邏輯。APT 的原理就是在注解了某些代碼元素(如字段、函數(shù)、類等)后,在編譯時(shí)編譯器會(huì)檢查 AbstractProcessor 的子類,并且自動(dòng)調(diào)用其 process() 方法,然后將添加了指定注解的所有代碼元素作為參數(shù)傳遞給該方法,開(kāi)發(fā)者再根據(jù)注解元素在編譯期輸出對(duì)應(yīng)的 Java 代碼。
3、示例
實(shí)現(xiàn)一個(gè)輕量的 “ButterKnife”,實(shí)現(xiàn)一個(gè)輕量的控件綁定框架,即通過(guò)注解來(lái)自動(dòng)生成 findViewById() 代碼。
3.1、創(chuàng)建項(xiàng)目
首先在工程中新建一個(gè)Java Library,命名為 apt_processor,用于存放 AbstractProcessor 的實(shí)現(xiàn)類。再新建一個(gè) Java Library,命名為 apt_annotation ,用于存放各類注解。
當(dāng)中,apt_processor 需要導(dǎo)入如下依賴:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc6'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt_annotation')
}
Android Plugin for Gradle >= 3.4 或者 Gradle Version >=5.0 都要在自己的annotation processor工程里面增加如下的語(yǔ)句:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
當(dāng)中,JavaPoet 是 square 開(kāi)源的 Java 代碼生成框架,可以很方便地通過(guò)其提供的 API 來(lái)生成指定格式(修飾符、返回值、參數(shù)、函數(shù)體等)的代碼。auto-service 是由 Google 開(kāi)源的注解注冊(cè)處理器。
實(shí)際上,上面兩個(gè)依賴庫(kù)并不是必須的,可以通過(guò)硬編碼代碼生成規(guī)則來(lái)替代,但還是建議使用這兩個(gè)庫(kù),因?yàn)檫@樣代碼的可讀性會(huì)更高,且能提高開(kāi)發(fā)效率。
app Module 需要依賴這兩個(gè)Library:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(':apt_annotation')
annotationProcessor project(':apt_processor')
}
這樣子,我們需要的所有基礎(chǔ)依賴關(guān)系就搭建好了:

3.2、自定義注解類
我們先聲明一個(gè)注解,類似ButterKnife,BindView 注解的聲明如下所示,放在 apt_annotation 中:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS):表示編譯時(shí)注解
@Target(ElementType.FIELD):表示注解范圍為類成員(構(gòu)造方法、方法、成員變量)
注解值 value 用于聲明 viewId
3.2、定義注解處理器
在 apt_processor Module 中創(chuàng)建 BindViewProcessor 類并繼承 AbstractProcessor 抽象類,該抽象類含有一個(gè)抽象方法 process() 以及一個(gè)非抽象方法 getSupportedAnnotationTypes() 需要由我們來(lái)實(shí)現(xiàn)。如果找不到AbstractProcessor類,則需要檢查一下自己創(chuàng)建的Module對(duì)不對(duì),類型必須是Java Library。
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = "BindViewProcessor";
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
- init:初始化??梢缘玫絇rocessingEnviroment,ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer;
- getSupportedAnnotationTypes:用于指定該 AbstractProcessor 的目標(biāo)注解對(duì)象,這里說(shuō)明是注解BindView;
- getSupportedSourceVersion:指定使用的Java版本,通常這里返回SourceVersion.latestSupported();
- process:用于處理包含指定注解對(duì)象的代碼元素??梢栽谶@里寫掃描、評(píng)估和處理注解的代碼,生成Java文件。
要自動(dòng)生成 findViewById() 方法,則需要獲取到控件變量的引用以及對(duì)應(yīng)的 viewid,所以需要先遍歷出每個(gè) Activity 包含的所有注解對(duì)象。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取所有包含BindView注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因?yàn)?BindView 的作用對(duì)象是 FIELD,因此 element 可以直接轉(zhuǎn)化為 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封裝此 Element 的最里層元素
//如果 Element 直接封裝在另一個(gè)元素的聲明中,則返回該封裝元素
//此處表示的即 Activity 類對(duì)象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//獲取注解值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//將每個(gè)包含了 BindView 注解的字段對(duì)象以及其注解值保存起來(lái)
variableElementMap.put(viewId, variableElement);
}
return true;
}
通過(guò)roundEnvironment.getElementsAnnotatedWith(BindView.class)得到所有注解elements,Element 用于代表程序的一個(gè)元素,這個(gè)元素可以是:包、類、接口、變量、方法等多種概念。這里以 Activity 對(duì)象作為 Key ,通過(guò) map 來(lái)存儲(chǔ)不同 Activity 下的所有注解對(duì)象。
3.3、生成代碼
獲取到所有的注解對(duì)象后,就可以來(lái)構(gòu)造 bind() 方法了。MethodSpec 是 JavaPoet 提供的一個(gè)概念,用于抽象出生成一個(gè)函數(shù)時(shí)需要的基礎(chǔ)元素,直接看以下方法應(yīng)該就可以很容易理解其含義了。
通過(guò) addCode() 方法把需要的參數(shù)元素填充進(jìn)去,循環(huán)生成每一行 findViewById 方法:
/**
* 生成方法
*
* @param typeElement 注解對(duì)象上層元素對(duì)象,即 Activity 對(duì)象
* @param variableElementMap Activity 包含的注解對(duì)象以及注解的目標(biāo)對(duì)象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法參數(shù)名
String parameter = "_" + StringUtils.decapitalize(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的對(duì)象類型的全名稱
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
完整代碼如下:
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = "BindViewProcessor";
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取所有包含BindView注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因?yàn)?BindView 的作用對(duì)象是 FIELD,因此 element 可以直接轉(zhuǎn)化為 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封裝此 Element 的最里層元素
//如果 Element 直接封裝在另一個(gè)元素的聲明中,則返回該封裝元素
//此處表示的即 Activity 類對(duì)象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//獲取注解值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//將每個(gè)包含了 BindView 注解的字段對(duì)象以及其注解值保存起來(lái)
variableElementMap.put(viewId, variableElement);
}
for (TypeElement key : typeElementMapHashMap.keySet()) {
Map<Integer, VariableElement> elementMap = typeElementMapHashMap.get(key);
PackageElement packageElement = mElementUtils.getPackageOf(key);
String packageName = packageElement.getQualifiedName().toString();
JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 生成 Java 類
*
* @param typeElement 注解對(duì)象上層元素對(duì)象,即 Activity 對(duì)象
* @param variableElementMap Activity 包含的注解對(duì)象以及注解的目標(biāo)對(duì)象
* @return
*/
private TypeSpec generateCodeByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
//自動(dòng)生成的文件以 Activity名 + ViewBinding 進(jìn)行命名
return TypeSpec.classBuilder(typeElement.getQualifiedName().toString() + "ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodByPoet(typeElement, variableElementMap))
.build();
}
/**
* 生成方法
*
* @param typeElement 注解對(duì)象上層元素對(duì)象,即 Activity 對(duì)象
* @param variableElementMap Activity 包含的注解對(duì)象以及注解的目標(biāo)對(duì)象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法參數(shù)名
String parameter = "_" + StringUtils.decapitalize(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的對(duì)象類型的全名稱
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
}