android studio 编译时注解(一) 工作原理解析

来源:互联网 发布:质量好的衣服淘宝店铺 编辑:程序博客网 时间:2024/05/29 12:04

本教程是基于android studio 3.0+来实践的

为什么要用编译时注解?因为运行时注解是十分消耗资源的,Eventbus的低版本就是采用运行时注解,被吐槽效率低下,所以新版本的Eventbus都是采用编译时注解注入的,其效率基本和直接写代码没什么区别

在整理思路之前,我们来了解一下,编译时注解和运行时注解

运行时注解通常被定义的注解需要通过反射来获取相关值编译时注解在代码构建编译过程的时候,生成java文件然后供需要的类进行调用两者根本区别在于,前者是程序员预先写好的java文件中,直接调用的,而后者是程序员写好java代码的生成规则,程序员自己不写java文件,交给编译器去写java文件,,java文件只有编译器编译完成后才能调用.

图解工程

这里写图片描述

第一点

app主工程
build.gradle中需要依赖lib.annotation,并且指定一个编译时注解器

dependencies {      annotationProcessor project(':lib.annotation.process')  //指定注解器(图中的3)    implementation project(':lib.annotation')  //依赖注解类工程(图中2)}

解析:当gradle运行编译的时候,会进入到lib.annotation.process工程中,对整个app工程使用了lib.annotation工程的java文件进行检索,结果会在lib.annotation.process.AbstractProcessor.process方法中回调,最后process结束后将编译出来的java代码输入到app工程的build.generated.source.apt.debug.*中,到此为止,编译出来的java代码就跟自己创建的java文件一样调用

第二点

lib.annotation是一个java library工程,,注意,是java library工程,不是model
主要是负责定义注解类,图中可见,定义了2个注解类

第三点(重点)

用于编译时注解的工程,也是一个java library工程,因为需要用到注解类,所以需要依赖lib.annotation
build.gradle配置如下

dependencies {     implementation 'com.squareup:javapoet:1.9.0'  //避免徒手写java代码的裤子    implementation project (':lib.annotation')         //依赖注解工程}

HzcInjectProcess类是主要工作的类,由它负责实现AbstractProcessor的process的方法.就是上方第一点说的检索回调
其他的两个文件都不是什么重要文件,主要是用些编写一些业务逻辑用的

第四点

如果知道SPI的人应该知道这个是什么,,其实这个就是以SPI的模式进行工作的,需要实现类为
javax.annotation.processing.Processor
如果不懂什么是SPI,就按照工程中的模样直接敲就好了,,固定的
文件里就写上你实现了Processor类的完整路径,在这个项目里,是这样的
com.hzc.process.HzcInjectProcess

现在我们重点来看HzcInjectProcess类

@SupportedAnnotationTypes({"com.hzc.annotation.Service", "com.hzc.annotation.Autoware"})  //告诉这个实现类,需要监听检索哪些注解@SupportedSourceVersion(SourceVersion.RELEASE_7)//最低支持的源码版本,这里是java7public class HzcInjectProcess extends AbstractProcessor {    //获得一些代码输出成文件的工具类    ProcessingEnvironment processingEnvironment;    public static final String PACKAGE_NAME = "com.hzc.inject";    public static final String CLASS_NAME = "HzcInject";    @Override    public synchronized void init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        //赋值代码输出成文件的工具类        this.processingEnvironment = processingEnvironment;    }    /**    * 主要检索回调的入口,所以编译动作从这里开始    * RoundEnvironment 包含了检索的结果,也就是说,哪些类用了上面SupportedAnnotationTypes中指定的注解类,以及相关可操作的值,反正一切和注解有关的都从这里获取    */    @Override    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        List<JavaFile> javaFiles = new ArrayList<>();        List<String> serviceList = new ArrayList<>();        //得到所有用到Service注解的元素        for (Element p : roundEnvironment.getElementsAnnotatedWith(Service.class)) {            try {                serviceList.add(p.toString());            } catch (Exception e) {            }        }        //havapoet生成java文件,不熟悉javapoet的同学自己查资料吧        TypeSpec hzcInject = TypeSpec.classBuilder(CLASS_NAME)                .superclass(ParameterizedTypeName.get(LinkedHashMap.class, String.class, String.class))                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)                .addMethod(BuildServiceCode.createConstructor())                .addMethod(BuildServiceCode.creaqteInject())                .addMethod(BuildServiceCode.createStaticInstance())                .addType(BuildServiceCode.createInnerClass())                .addMethod(BuildServiceCode.createInitService(serviceList)).build();        javaFiles.add(JavaFile.builder(PACKAGE_NAME, hzcInject).build());        Map<String, List<Element>> activitys = new HashMap<>();        for (Element p : roundEnvironment.getElementsAnnotatedWith(Autoware.class)) {            String key = p.getEnclosingElement().toString();            List<Element> elementList = activitys.get(key);            if (elementList == null)                elementList = new ArrayList<>();            elementList.add(p);            activitys.put(key, elementList);        }        for (String key : activitys.keySet()) {            List<Element> elementList = activitys.get(key);            javaFiles.add(JavaFile.builder(PACKAGE_NAME + ".model", BuildInjectModuleCode.createInjectClass(key, elementList)).build());        }        int size = javaFiles.size();        for (int i = 0; i < size; i++) {            try {                javaFiles.get(i).writeTo(processingEnv.getFiler());            } catch (IOException e) {            }        }        //告诉处理器,,处理完成了        return true;    }}

解析:

创建一个类去实现javax.annotation.processing.AbstractProcessor,同时将这个类的完整包路径写入到resources.META-INF.services.javax.annotation.processing.Processor文件中.
在类的头部告诉处理器需要处理检索的注解类,并制定编译版本,然后在process中对检索的结果进行处理.
roundEnvironment.getElementsAnnotatedWith(Service.class)//获得所有使用了这个注解的对象
Element.toString //获得当前使用了注解的对象的完整包类名,如果作用在属性,那就是属性名,如果作用在类
那么就是类名
Element.getEnclosingElement().toString()//当作用在属性的时候,获得这个属性所在的类全称
Element.getAnnotationMirrors()//获得元素的所有注解
Element.getAnnotationMirrors().get(0).getElementValues()//获得注解的值Map对象
Element.getSimpleName()//获得使用了注解的类/属性的名称
Element.asType()//获得使用了注解的类/属性的类型

到此,完成一个编译时注解基本流程,剩下的工作就是,把原来运行时注解的代码用javapoet进行翻译就可以了.

如果想了解如何进行类似Evenbus,buffknife,spring之类的注入工作,看后面的篇章,代码也会在后面的篇章贴上

原创粉丝点击