Android自定义注解

按照Tatget来分的话,注解有10种类型。Target是声明该注解使用的地方,好比使用在属性上是field,使用在方法上是method,这里就不一一说明了,感兴趣的同窗能够深刻研究一下其余几种。按照Retention来分的话,注解有3种类型,分别是SOURCE(源码)、CLASS(编译)、RUNTIME(运行)。本文主要按照Retention,也就是生命周期方向来区别讲解注解。源码注解在编译时使用,好比@overide,生命周期短只在编译时有做用,通常用在IDE代码检测;编译注解缺点是只在编译时有做用,优势是效率比运行时注解高,可是使用较复杂一些;运行时注解优势是在编译时和运行时都能工做,可是运行时经过反射来获取注解的值,效率较低。下面的两个图分别是系统定义的这2个方向类别枚举:




1.源码期注解(RetentionPolicy.SOURCE)java

源码注解通常在编译器IDE中使用,好比@overide。源码注解只在编译前起做用,通常用来约束代码规范,在实际的业务开发中并不常见。数组

2.编译期注解(RetentionPolicy.CLASS)bash

编译期注解通常结合autoService+javapoet一块儿使用,用来在编译期生成简单重复的文件,节约手写的时间。下面咱们一块儿来生成一个编译期注解,注意这里必定要新建一个java library来操做,由于核心的注解处理器父类AbstractProcessor类只在java library中有,在Android中是找不到的。假设咱们须要生成的效果以下:app


第一步 定义注解类ide

第二步 写好注解处理器测试

注解处理器经过继承AbstractProcessor的方式实现,在编译的时候系统会寻找到全部AbstractProcessor子类执行其中的process方法,因此咱们能够在process中进行生成java文件的操做,生成之后系统会将生成的java文件打包成class。gradle

1.其中须要注意的是,这里须要导入autoservice类库,而后注解处理器类须要经过@AutoService(Processor.class)进行标注。这是固定写法,意思是将该类的路径告诉注解处理器,否则系统不知道处理器路径是无法生成文件的。在编译后,这个路径就会被保存在javalibrary的build/META-INF下面,其中内容就是咱们自定义注解处理器的完整路径。固然若是不用@AutoService标注,直接在META-INF目录下新建文件也是能够的,只是更麻烦,使用@AutoService方式进行标注的使用方式以下图:ui

implementation 'com.google.auto.service:auto-service:1.0-rc2'复制代码

2.标注好了通常须要复写其中的4个方法,分别是init、getSupportedAnnotationTypes、getSupportedSourceVersion、process。其中process是最重要的,也是最复杂的。咱们先复写前面3个,process咱们单独拿出来写。前面3个方法复写以下:google

其中init主要是拿到注解处理类。其中最重要的是拿到filer,这个在写文件的时候要用到,因此必需要复写init拿到这个filer。elementUtils主要是用来获取到使用了注解的类基本信息。getSupportedAnnotationTypes是用来告诉系统编译时支持哪些注解,在这里咱们把咱们自定义的注解丢进去。getSupportedSourceVersion通常就写SourceVersion.latestSupported就行了。再提一句,这个getSupportedAnnotationTypes和getSupportedSourceVersion方法也能够不复写,可是要在MyAbstractor类上用注解的方式标明。spa

3.接下来咱们来写最重要的process方法,这个方法是整个apt最核心的部分。这里能够彻底手写可是容易错,速度也慢,因此通常导入javapoet类库,借助javapoet来写。咱们process方法实现以下图,在process方法中首先遍历全部注解,找到咱们自定义的MyAnotation注解。而后拿到MyAnotation对象,获取到使用处给予的注解的值。接下来经过javapoet中的TypeSpec来拼接出须要生成的java类。最终调用最核心的方法JavaFile.builder(生成的路径,拼接好的格式).build().writeTo(注解处理器的处理对象)。这样就写好了注解处理器的内容,接下来就坐等使用和编译生成就好啦!这里须要注意的是,因为注解处理器是在java library中,因此不能经过Log类来打点,这里能够经过java的输出方式System.out.print()的方式打印出数据来进行调试,也能够经过从init方法中拿到Message对象来打日志,而后在gradle console窗口进行查看日志信息

compile 'com.squareup:javapoet:1.7.0'复制代码

提问:核心的process方法中两个参数分别表明什么?

第一个参数是set<TypeElement>,其中TypeElement表明类元素,也就是全部使用了指定注解的全部类,这个指定注解是指上面getSupportedAnotationTypes指定的注解,好比说本例子中在MainActivity中使用了MyAnotation,因为MyAnotation在指定的注解中,因此MainActivity这个类就会出如今该集合中。

第二个参数roundEnviroment是保存了使用了指定注解的全部注解项信息,不论是使用在类上仍是属性上仍是方法上,都会放到该参数中。在本例子中咱们获取到全部使用了MyAnotation注解的注解项,而且取出了注解的值。

第三步 使用注解

因为注解是在javalibrary中定义的,咱们首先要将该library导入到app模块中。须要注意的是类库声明和注解声明都须要作,否则是无法跑起来的。而后咱们给类中的随意一个属性上加上咱们的注解,固然方法和类也是能够的,这里就不一一演示了。注解使用之后咱们接下来在onCreate方法里面调用这个经过注解处理器生成的MySimpleAptName类中的test静态方法来测试一下是否能够调用到,须要注意的是咱们项目里根本没有去手写这个类,因此直接这样写报错也很正常,必定要rebuild之后等该类生成了那么就不会报错了。

implementation project(':myapt')
annotationProcessor project(':myapt')复制代码


第四步 自动生成文件

在运行项目或者rebuild的时候,会在使用时指定好的目录生成class文件,注意这里clean的话是不能生成的。最后生成的效果以下,是否是和咱们本身写出来的类很像呢?这个时候回过头去看MainActivity里已经没有报错了,至此编译时注解demo圆满完成。


3.运行期注解(RetentionPolicy.RUNTIME)

运行期注解的生命周期最长,能够存活到程序运行时,也是使用最普遍的注解类型。通常用来代替项目中重复简单的代码,设置一个注解可比写不少无用代码要简洁得多。编译器注解能够经过反射来获取到注解类,拿到咱们使用注解时设置的内容。不过因为使用反射,因此对效率会有一些影响,这也是不少开源项目(BufferKnief、Retrofit等)选择编译期注解的缘由。不过写起来很简单,只须要三步就OK啦!不像编译期注解要结合其余功能类,接下来咱们一块儿来手写一个运行期注解。

第一步 定义注解类


第二步 使用注解类

第三步 解析注解类

咱们须要在运行的时候对注解进行解析。如上图,咱们在页面的oncreate方法执行时首先获取到该类全部的注解,获取的值是一个Field[]数组。这里面每个Field都表明MainActivity类中的一个注解使用项,接下来咱们遍历全部的注解项,对本身关心的项进行具体的处理。demo中咱们对TextView进行了找控件的操做,这样就统一处理了从而避免了项目中出现不少findViewById的操做,让咱们的代码变得更简洁了。

最后:本人小萌新,若是本文有写错的或者描述不全的地方还望各位大佬多多指点,一块儿探讨,共同进步!

相关文章
相关标签/搜索