Android AnnotationProcessor

Android AnnotationProcessor

  • 一.项目结构
  • 二.定义注解
  • 三.实现注解处理器
    • (一)依赖
    • (二)注解处理器
    • (三)处理注解
  • 四.使用注解处理器
    • (一)依赖
    • (二)使用注解
    • (三)生成的代码
  • 五.注意事项

注解处理器通常可以用在模块间解藕、自动生成代码等地方,比如router路由或者butterknife。效果就是我们在某些地方标注某些注解,在编译时,注解处理器会扫描这些注解的地方,然后生成一些代码,这样做可以实现全局的一些自动化功能,并不用开发者感知,非常方便,有点类似gradle插件。

这里以一个demo演示下通常需要怎么实现注解处理器的功能。
demo的功能很简单:通过注解的方式,自动生成接口和实现类的注册关系,在主项目里可以直接调用接口方法,不用注册。

一.项目结构

首先我们创建一个Android项目,添加两个module。
Android AnnotationProcessor_第1张图片

  • app:项目主module
  • lib_annotations:定义注解的java-module,通常会把一些功能注解,定义为一个aar
  • lib_compiler:定义注解处理器的java-module,不会被打入apk,只在编译时使用

二.定义注解

首先在lib_annotations里定义我们的注解。

@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.TYPE)
public @interface ByteService {
    Class<?> clazz() default Object.class;
}

这里定义了一个ByteService的注解类,有一个clazz参数用于指定对应的服务接口,默认为Object.class。
@Retention(RetentionPolicy.SOURCE)指定注解只保留在源文件中,不会被保留到class字节码文件中:因为在编译前期通过扫描源文件就使用完了注解。
@Target(value = ElementType.TYPE)指定该注解只能使用在类上:因为我们的功能是注册接口和实现类的关系。

三.实现注解处理器

接着在lib_compiler里定义你注解处理器。

(一)依赖

dependencies {
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    implementation 'com.squareup:javapoet:1.11.1'
    implementation project(':lib_annotations')
}

首先引入相关依赖:

  • auto-service:用于自动识别注解处理器的功能
  • javapoet:用于生成java代码的工具sdk
  • lib_annotations:要使用上面定义的注解,引入本地库

(二)注解处理器

然后定义处理类,处理类要继承自AbstractProcessor类。

@AutoService(Processor.class)
public class ByteProcessor extends AbstractProcessor {}

使用@AutoService(Processor.class)注解,会在编译时,由auto-service库在jar包的/java/main/META-INF/services/下生成一个javax.annotation.processing.Processor文件,内容就是注解处理器类的类名,也是后续编译器识别的标示。
Android AnnotationProcessor_第2张图片
在这里插入图片描述

(三)处理注解

我们来看看Processor的具体实现。

private Filer filer;
private Messager messager;
private Map<String, String> mapper = new HashMap<>();

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    filer = processingEnvironment.getFiler();
    messager = processingEnvironment.getMessager();
}

首先init()方法可以通过全局的ProcessingEnvironment环境对象,获取一些功能对象。

  • Filer:用于生成新的java文件的对象
  • Messager:用于输出log的对象
@Override
public Set<String> getSupportedAnnotationTypes() {
    Set<String> res = new HashSet<>();
    res.add(ByteService.class.getCanonicalName());
    return res;
}

getSupportedAnnotationTypes()方法用于返回该Processor想要接收处理的注解,要返回全路径类名,通常使用getCanonicalName()方法。该方法也可以通过在Processor类上定义SupportedAnnotationTypes注解的方式指定。

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //获取该注解的元素
    Set<? extends Element> sets = roundEnvironment.getElementsAnnotatedWith(ByteService.class);
    if (sets != null && sets.size() > 0) {
        for (Element element : sets) {
            //每一个元素由于只能是类,所以都是TypeElement类型
            if (element instanceof TypeElement) {
                //获取定义你该注解的元素(这里是类)的全路径名称
                String implName = TypeName.get(element.asType()).toString();
                //对应的接口全路径类名
                String interName;
                try {
                    //通过注解的clazz对象直接获取  
                    interName = element.getAnnotation(ByteService.class).clazz().getCanonicalName();
                } catch (MirroredTypeException mte) {
                    //由于调用clazz对象时,可能因为Class对象还没有被加载,所以抛异常
                    //异常中有相关class对象的信息,直接拿到类名即可
                    interName = TypeName.get(mte.getTypeMirror()).toString();
                }
                //如果没有定义你clazz(默认为Object),则取该类默认实现的接口
                if (Object.class.getCanonicalName().equals(interName)) {
                    List<? extends TypeMirror> typeMirrors = ((TypeElement) element).getInterfaces();
                    interName = TypeName.get(typeMirrors.get(0)).toString();
                }
                //放入map中后续生成代码
                mapper.put(interName, implName);
                //messager输出log
                messager.printMessage(Diagnostic.Kind.NOTE, "Interface: " + interName + " Impl: " + implName);
            }
        }
        //生成代码
        generate();
    }
    return true;
}

注释已经解释的很清楚,大概分几步:

  1. 获取指定注解的所有元素
  2. 拿到元素的类名
  3. 拿到元素标注的接口的类名
  4. 存入map,输出log
  5. 开始生成代码
private void generate() {
    //private constructor
    MethodSpec cons = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
    //static map
    ParameterizedTypeName mapType = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Class.class), ClassName.get(Object.class));
    FieldSpec map = FieldSpec.builder(mapType, "services", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("new java.util.HashMap<>()").build();
    //static init
    FieldSpec init = FieldSpec.builder(Boolean.class, "isInit", Modifier.PRIVATE, Modifier.STATIC).initializer("false").build();
    //static getService
    MethodSpec.Builder getServiceBuilder = MethodSpec.methodBuilder("getService").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
    TypeVariableName t = TypeVariableName.get("T");
    TypeVariableName b = TypeVariableName.get("B").withBounds(t);
    getServiceBuilder.addTypeVariable(t).addTypeVariable(b);
    getServiceBuilder.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), t), "clazz");
    getServiceBuilder.returns(b);
    //statement
    getServiceBuilder.beginControlFlow("if(!isInit)");
    generateInitStatement(getServiceBuilder).addStatement("isInit=true").endControlFlow();
    getServiceBuilder.addStatement("return (B) services.get(clazz)");
    //class
    TypeSpec typeSpec = TypeSpec.classBuilder("ServiceManager")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addField(init)
            .addField(map)
            .addMethod(cons)
            .addMethod(getServiceBuilder.build())
            .build();
    //file
    JavaFile javaFile = JavaFile.builder("com.util.service", typeSpec).build();
    try {
        javaFile.writeTo(filer);
    } catch (IOException e) {
    }
}

生成代码主要是使用javapoet提供的功能:

  1. MethodSpec生成方法和构造器
  2. FieldSpec生成字段
  3. TypeVariableName生成泛型定义
  4. ParameterizedTypeName生成类型,可以包含泛型
  5. TypeSpec生成类
  6. JavaFile生成文件
  7. Filer提供写入文件功能

这里注册代码是通过generateInitStatement()方法,把map里的所有对象直接插入到代码里实现的。

private MethodSpec.Builder generateInitStatement(MethodSpec.Builder getServiceBuilder) {
    for (Map.Entry<String, String> entry : mapper.entrySet()) {
        getServiceBuilder.addStatement(String.format("services.put(%s.class,new %s())", entry.getKey(), entry.getValue()));
    }
    return getServiceBuilder;
}

至此就完成了注解处理器的定义。

四.使用注解处理器

完成类注解处理器,主module就可以使用了。

(一)依赖

dependencies {
    ...
    annotationProcessor project(':lib_complier')
    implementation project(':lib_annotations')
}

我们要使用注解,所以要引入lib_annotations库。
引入注解处理器时,使用annotationProcessor即可,在编译时,会自动识别上面说的META-INF里的类名,找到类进行注解处理器的执行。

(二)使用注解

我们定义了两个接口和两个实现类。
Android AnnotationProcessor_第3张图片

@ByteService
public class Service1Impl implements IService1 {
    @Override
    public void doFun() {
        System.out.println("doFun");
    }
}

@ByteService(clazz = IService2.class)
public class Service2Impl implements ITest, IService2 {

    @Override
    public void doTest() {
        System.out.println("doTest");
    }

    @Override
    public void test() {
        System.out.println("test");
    }
}

其中Service1Impl由于只有一个接口所以采用默认的注解,Service2Impl有两个接口,指定注解的clazz接口为IService2。
在我们build一次后,就可以直接调用ServiceManager使用。

ServiceManager.getService(IService1.class).doFun();
ServiceManager.getService(IService2.class).doTest();

(三)生成的代码

而注解处理器生成的代码如下。

public final class ServiceManager {
  private static Boolean isInit = false;

  private static final Map<Class, Object> services = new java.util.HashMap<>();

  private ServiceManager() {
  }

  public static <T, B extends T> B getService(Class<T> clazz) {
    if(!isInit) {
      services.put(com.example.byteapplication.annotation_process.IService1.class,new com.example.byteapplication.annotation_process.Service1Impl());
      services.put(com.example.byteapplication.annotation_process.IService2.class,new com.example.byteapplication.annotation_process.Service2Impl());
      isInit=true;
    }
    return (B) services.get(clazz);
  }
}

五.注意事项

  1. javapoet生成类时,需要注意import相关类,或者通过全路径类名使用参考文章
  2. 注解处理器也可以debug调试参考文章
  3. 使用java注解处理器处理不了kotlin相关的代码,可以使用kapt参考文章

你可能感兴趣的:(知识积累,android相关)