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```
  */
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
        ClassName.get(Map.class),
        ClassName.get(String.class),
        ClassName.get(RouteMeta.class)
);
        
/*
 * Build input param name.
 * 生成参数 (Map atlas)
 */
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();

  • 2.生成方法
 /**
 *  生成  @Override
 *       public void loadInto(Map 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(){{" + 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 可以生产很多结构性的代码,可以节约我们日常开发的时间,它的使用也比较简单。

参考

  • 如何在Android中完成一个APT项目的开发

你可能感兴趣的:(Android)