Android 中使用 APT

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 可以生產很多結構性的代碼,可以節約我們日常開發的時間,它的使用也比較簡單。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章