簡(jiǎn)介
APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具,確切的說(shuō)它是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解。注解處理器以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成.java文件作為輸出。
簡(jiǎn)單來(lái)說(shuō)就是在編譯期,通過(guò)注解生成.java文件。
作用
使用APT的優(yōu)點(diǎn)就是方便、簡(jiǎn)單,可以少些很多重復(fù)的代碼。根據(jù)規(guī)則,幫我們生成代碼、生成類文件,如ButterKnife、Dagger、EventBus等注解框架。
Element 程序元素
| 元素 | 描述 |
|---|---|
| PackageElement | 表示一個(gè)包程序元素,提供對(duì)有關(guān)包及其成員的信息的訪問(wèn) |
| ExecutableElement | 表示某個(gè)類或接口的方法、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗?/td> |
| TypeElement | 表示一個(gè)類或接口程序元素,提供對(duì)有關(guān)類型及其成員的信息的方法 |
| VaribaleElement | 表示一個(gè)字段、enum常量、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù) |
常用API
| 方法 | 描述 |
|---|---|
| getEnclosedElements() | 返回該元素直接包含的子元素 |
| getEnclosingElement() | 返回包含該element的父element,與上一個(gè)方法相反 |
| getKind() | 返回element的類型,判斷是那種element |
| getModifiers() | 獲取修飾關(guān)鍵字,入public static final 等關(guān)鍵字 |
| getSimpleName() | 獲取名字,不帶包名 |
| getQualifiedName() | 獲取全名,如果是類的話,包含完整的包名路徑 |
| getParameters() | 獲取方法的參數(shù)元素,每個(gè)元素是一個(gè)VariableElement |
| getReturnType() | 獲取方法元素的返回值 |
| getConstantValue() | 如果屬性變量被final修飾,則可以使用該方法獲取它的值 |
Demo
項(xiàng)目結(jié)構(gòu)
- 創(chuàng)建Android Module命名為app
- 創(chuàng)建Java library Module命名為 apt-annotation 存放自定義注解
- 創(chuàng)建Java library Module命名為 apt-compiler 依賴 apt-annotation、auto-service 指定注解處理器
- 創(chuàng)建Android library Module 命名為 apt-library 存放工具類
==注==:為什么兩個(gè)模塊一定要是Java Library?如果創(chuàng)建Android Library模塊會(huì)發(fā)現(xiàn)不能找到AbstractProcessor這個(gè)類,這是因?yàn)锳ndroid平臺(tái)是基于OpenJDK的,而OpenJDK中不包含APT的相關(guān)代碼。因此,在使用APT時(shí),必須在Java Library中進(jìn)行。
環(huán)境配置
apt-annotation 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
...
// DES: 導(dǎo)入annotation庫(kù), 因lib中使用到了內(nèi)置注解
implementation 'androidx.annotation:annotation:1.0.0@jar'
}
// DES: 指定編碼格式
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// DES: 指定Java JDK 兼容及目標(biāo)版本
// DES: 7 表示 Java JDK 1.7
sourceCompatibility = "7"
targetCompatibility = "7"
apt-compiler 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
...
// DES: 依賴自定義注解
implementation project(":apt-annotation")
// DES: 導(dǎo)入官方APT處理Service
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
// DES: 導(dǎo)入JavaPoet庫(kù)用于類調(diào)用的形式來(lái)生成Java代碼
implementation 'com.squareup:javapoet:1.9.0'
}
// DES: 指定編碼格式
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// DES: 指定Java JDK 兼容及目標(biāo)版本
// DES: 7 表示 Java JDK 1.7
sourceCompatibility = "7"
targetCompatibility = "7"
版本問(wèn)題
// Android Studio 3.3.2 + Gradle 4.10.1 (臨界版本)
// 注冊(cè)注解,并對(duì)其生成META-INF的配置信息,rc2在gradle 5.1.1后有坑
// AS3.3.2 + Gradle 4.10.1 + auto-service:1.0-rc2
implementation 'com.google.auto.service:auto-service:1.0-rc2'
// Android Studio 3.4.1 + Gradle 5.1.1(向下兼容)
// AS3.4.1 + Gradle 5.1.1 + auto-service:1.0-rc4
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
apt-library 無(wú)需依賴
app 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
...
// 導(dǎo)入library
implementation project(":apt-library")
// 導(dǎo)入自定義注解
implementation project(":apt-annotation")
// 指定注釋處理器
annotationProcessor project(":apt-compiler")
...
}
相關(guān)代碼
apt-annotation 自定義注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義注解 @BindView 牛油刀
* @author XGY
*/
@Target(ElementType.FIELD) // 作用于成員屬性上
@Retention(RetentionPolicy.RUNTIME) // 指定為運(yùn)行時(shí)
public @interface BindView {
int value();
}
apt-compiler
節(jié)點(diǎn)信息
/**
* 節(jié)點(diǎn)信息Bean
* @author XGY
*/
public class NodeInfo {
/** 包路徑 */
private String packageName;
/** 節(jié)點(diǎn)所在類名稱 */
private String className;
/** 節(jié)點(diǎn)類型名稱 */
private String typeName;
/** 節(jié)點(diǎn)名稱 */
private String nodeName;
/** 注解的value */
private int value;
public NodeInfo(String packageName, String className, String typeName,
String nodeName, int value) {
this.packageName = packageName;
this.className = className;
this.typeName = typeName;
this.nodeName = nodeName;
this.value = value;
}
public String getPackageName() { return packageName; }
public String getClassName() { return className; }
public String getTypeName() { return typeName; }
public String getNodeName() { return nodeName; }
public int getValue() { return value; }
}
處理器
import com.example.annotation.BindView;
import com.example.compiler.bean.NodeInfo;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.Processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* 自定義注解 @BindView 編譯器
* @author XGY
*/
@AutoService(Processor.class) // 指定注解處理器
@SupportedAnnotationTypes("com.example.annotation.BindView") // 指定需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 指定Java JDK編譯版本
public class BindViewProcessor extends AbstractProcessor {
/** Element操作類 */
private Elements mElementUtils;
/** 類信息工具類 */
private Types mTypeUtils;
/** 日志工具類 */
private Messager mMessager;
/** 文件創(chuàng)建工具類 */
private Filer mFiler;
/** 節(jié)點(diǎn)信息緩存 */
private Map<String, List<NodeInfo>> mCache = new HashMap<>();
/** 初始化 */
@Override
public synchronized void init(ProcessingEnvironment environment) {
super.init(environment);
// 獲取相關(guān)工具
mElementUtils = environment.getElementUtils();
mTypeUtils = environment.getTypeUtils();
mMessager = environment.getMessager();
mFiler = environment.getFiler();
// 打印Build提示
mMessager.printMessage(Diagnostic.Kind.NOTE, "開始處理自定義 @BindView 注解");
}
/** 處理注解 */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 判斷是否有使用 @BindView 注解
if (annotations != null && !annotations.isEmpty()) {
// 獲取所有 @BindView 節(jié)點(diǎn)
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
// 判斷節(jié)點(diǎn)集合不為空
if (elements != null && !elements.isEmpty()) {
// 循環(huán)遍歷節(jié)點(diǎn)
for (Element element : elements) {
// 獲取節(jié)點(diǎn)包信息
String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
// 獲取節(jié)點(diǎn)類信息,由于 @BindView 作用于成員屬性上,所以這里使用 getEnclosingElement() 獲取父節(jié)點(diǎn)信息
String className = element.getEnclosingElement().getSimpleName().toString();
// 獲取節(jié)點(diǎn)類型
String typeName = element.asType().toString();
// 獲取節(jié)點(diǎn)標(biāo)記的屬性名稱
String simpleName = element.getSimpleName().toString();
// 獲取注解的值
int value = element.getAnnotation(BindView.class).value();
// 打印
mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
mMessager.printMessage(Diagnostic.Kind.NOTE, "className:" + className);
mMessager.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName);
mMessager.printMessage(Diagnostic.Kind.NOTE, "simpleName:" + simpleName);
mMessager.printMessage(Diagnostic.Kind.NOTE, "value:" + value);
// 緩存KEY
String key = packageName + "." + className;
// 緩存節(jié)點(diǎn)信息
List<NodeInfo> nodeInfos = mCache.get(key);
// 判斷是否為空
if (nodeInfos == null) {
// 初始化
nodeInfos = new ArrayList<>();
// 載入
nodeInfos.add(new NodeInfo(packageName, className, typeName, simpleName, value));
// 緩存
mCache.put(key, nodeInfos);
} else {
// 載入
nodeInfos.add(new NodeInfo(packageName, className, typeName, simpleName, value));
}
}
// 判斷臨時(shí)緩存是否不為空
if (!mCache.isEmpty()) {
// 遍歷臨時(shí)緩存文件
for (Map.Entry<String, List<NodeInfo>> stringListEntry : mCache.entrySet()) {
try {
// 創(chuàng)建文件
createFile(stringListEntry.getValue());
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
}
return false;
}
/** 創(chuàng)建Java文件 */
private void createFile(List<NodeInfo> infos) throws IOException {
// 獲取第一個(gè)節(jié)點(diǎn)用來(lái)獲取信息
NodeInfo info = infos.get(0);
// 生成的類名稱
String className = info.getClassName() + "$$ViewBinding";
// JavaFileObject
JavaFileObject file = mFiler.createSourceFile(info.getClassName() + "." + className);
// Writer
Writer writer = file.openWriter();
// 設(shè)置包路徑
writer.write("package " + info.getPackageName() + ";\n\n");
// 設(shè)置類名稱
writer.write("public class " + className + " {\n\n");
writer.write("\tpublic static void bind(" + info.getClassName() + " target) {\n");
// 循環(huán)遍歷設(shè)置方法體
for (NodeInfo node : infos) {
writer.write("\t\ttarget." + node.getNodeName() + " = (" + node.getTypeName() +
") target.findViewById(" + node.getValue() + ");\n");
}
writer.write("\t}\n");
writer.write("}");
// 關(guān)閉
writer.close();
}
}
==注意:==
// 指定注解處理器
@AutoService(Processor.class)
// 指定需要處理的注解
@SupportedAnnotationTypes("com.example.annotation.BindView")
// 指定Java JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
關(guān)于文件生成可以使用JavaPoet,
相關(guān)地址:
Github
apt-library 工具類
import android.app.Activity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 自定義牛油刀工具類
* @author XGY
*/
public final class ButterKnife {
/** 綁定 */
public static void bind(Activity target) {
try {
// Activit類
Class clazz = target.getClass();
// 反射獲取apt生成的指定類
Class<?> bindViewClass = Class.forName(clazz.getName() + "$$ViewBinding");
// 獲取它的方法
Method method = bindViewClass.getMethod("bind", clazz);
// 執(zhí)行方法
method.invoke(bindViewClass.newInstance(), target);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
app 中使用
public class MainActivity extends AppCompatActivity {
// 使用自定義注解
@BindView(R.id.textView)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 綁定
ButterKnife.bind(this);
// 設(shè)置文案
textView.setText("ButterKnife 綁定成功");
}
}