APT(Annotation Processing Tool)註解處理器,是一種處理註解的工具。
APT 在編譯時期掃描源代碼中的註解,開發中根據註解,利用 APT 自動生成 Java 代碼,減少冗餘代碼和手動的代碼輸入,提高編碼效率。
APT 中的數據類型和概念
1. ProcessingEnvironment
在複寫 AbstractProcessor 的 init 方法時,參數就是一個 ProcessingEnvironment 對象。它內部提供一下實用對象,我們可以利用這些對象,來實現需要的功能。
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
...
}
2. Element
在 APT 階段,任何事物都被成爲元素。比如一個對象、一個類、一個方法、一個參數,它們都被統一稱爲元素。 Element 是個接口類,有很多子類。子類在其基礎上增加了額外的接口方法來描述具體事物的特殊屬性。
-
TypeElement: 一個類或接口程序元素
-
VariableElement: 一個字段、 enum 常量、方法或構造方法參數、局部變量或異常參數
-
ExecutableElement: 某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註解類型元素
-
PackageElement: 一個包程序元素
-
TypeParameterElement: 一般類、接口、方法或構造方法元素的泛型參數
2.ElementKind
是一個枚舉類,用於判斷元素;
包含 PACKAGE(包),CLASS(類),INTERFACE(接口),FIELD(變量),PARAMETER(參數),METHOD(方法)
3.Elements
處理 Element 對象的工具類
4. Filer
Filer 是一個文件操作的接口,可以創建或寫入一個 Java 文件。和一般的文件區別在於它是專門處理 Java 文件,以 .java 或 .class 爲後綴的文件。在 APT 中,在自動代碼生成後,用 Filer 生成一個 .java 或 .class 文件。
5. Types
是一個操作類型的工具類,也可以操作 TypeMirror 對象。
例如如果想知道變量的類型,可以通過 Types 相關類處理。
// 判斷是否爲 Parcelable 類型
String PARCELABLE = "android.os.Parcelable"
TypeMirror parcelableType = elements.getTypeElement(PARCELABLE).asType();
if (types.isSubtype(typeMirror, parcelableType)) {
// PARCELABLE
return TypeKind.PARCELABLE.ordinal();
}
6. TypeMirror
表示數據類型,包含基本類型 int、 boolean,也包含複雜類型,例如 自定義類、數組、Parcelable 等
7. Modifier
修飾詞。有 PUBLIC, PROTECTED, PRIVATE, ABSTRACT, DEFAULT等
8. RoundEnvironment
複寫 AbstractProcessor 的 process 方法,其中一個參數就是 RoundEnvironment。 可以通過 RoundEnvironment 對象獲取在代碼中設置的相關注解的 Element
ARouter 中的代碼生成
開源庫 ARouter 是一個廣泛應用的路由,它也是使用 APT 方式,生成相應的代碼。
在使用 APT 生成代碼使用了 JavaPoet , JavaPoet 使用比較簡單,根據官方文檔直接去操作即可。至於如何使用 JavaPoet ,請參考我之前的文章 JavaPoet 和 Java 註解在 Android 中的應用.
下面是以 ARouter 的 RouteProcessor 爲例,說明代碼生成的過程
RouteProcessor 使用生成類似下面這樣的一個 .java 文件,用於路由的調整,同時傳遞參數
package com.alibaba.android.arouter.routes;
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
}
}
我們看到上面生成的文件,想想是怎樣生成的
在 RouteProcessor#parseRoutes 方法中
- 1. 生成參數
/*
* 生成 ```Map<String, RouteMeta>```
*/
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
);
/*
* Build input param name.
* 生成參數 (Map<String, RouteMeta> atlas)
*/
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
- 2.生成方法
/**
* 生成 @Override
* public void loadInto(Map<String, RouteMeta> atlas)
*/
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
方法生成之後,就需要填充裏面 map 內容了
- 3.填充參數 map 的內容
// Make map body for paramsType
StringBuilder mapBodyBuilder = new StringBuilder();
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
// put("key1", 8);
}
}
String mapBody = mapBodyBuilder.toString();
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(), // "/test/activity2"
routeMetaCn, // RouteMeta
routeTypeCn, // RouteMeta
className, // Test2Activity
routeMeta.getPath().toLowerCase(), //"/test/activity2"
routeMeta.getGroup().toLowerCase()); // test
現在是所有內容都生成了,接下來就是要生成相應的 .java 文件了
- 4. 生成文件
// Generate groups
String groupFileName = NAME_OF_GROUP + groupName; // ARouter$$Group
JavaFile.builder(PACKAGE_OF_GENERATE_FILE, //package com.alibaba.android.arouter.routes
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS) // DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER.
.addSuperinterface(ClassName.get(type_IRouteGroup)) // 實現接口
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);
總結
APT 可以生產很多結構性的代碼,可以節約我們日常開發的時間,它的使用也比較簡單。