赞
踩
首先如果你对注解没有太多了解,建议先看一下我之前的两篇博客
Android注解:Java注解
Android注解:Android 常用注解
这两篇博客都详细介绍了关于Android注解的一些基础知识。这是Android自定义注解的第一篇,源码注解。Android注解类型可以分为三种,源码注解,字节码注解,运行时注解;每一种注解都有其特定的用途。我认为要想真正透彻的理解这三种注解,我们必须充分的了解其适应的场景。对于源码注解,只会在源码上保留,所以源码注解最适合的场景就是做代码正确性的检测。
前面瞎扯了这么多,我们先看一个非常简单的例子。
import android.support.annotation.IntDef; ... public static final int DISPLAY_USE_LOGO = 1; public static final int DISPLAY_SHOW_HOME = 2; public static final int DISPLAY_HOME_AS_UP= 4; public static final int DISPLAY_SHOW_TITLE= 8; public static final int DISPLAY_SHOW_CUSTOM =16; @IntDef(flag=true, value={ DISPLAY_USE_LOGO, DISPLAY_SHOW_HOME, DISPLAY_HOME_AS_UP, DISPLAY_SHOW_TITLE, DISPLAY_SHOW_CUSTOM }) @Retention(RetentionPolicy.SOURCE) public @interface DisplayOptions {} //使用 @DisplayOptions int a= DISPLAY_USE_LOGO; @DisplayOptions int b= DISPLAY_SHOW_HOME; @DisplayOptions int c= a|b;
我们自定义的注解DisplayOptions
在定义的地方使用的是@Retention(RetentionPolicy.SOURCE)
从这可以看出使用的是源码注解,再看注解的使用;注解DisplayOptions
实际上只是去限制int值的赋值范围,但是就算注解提示错误,给int变量赋予了其他值,也不会影响编译,注解只是检查了一下代码,给出对应的提示。
上面的例子非常简单,通过@IntDef或者@StringDef就可以实现,但是如果我们想实现一个像Override这样的注解,我们应该怎么做呢?首先我们来看一下Override的注解的源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
从源码可以看出,Override注解是源码级注解,作用的对象是方法。下面的例子我们来自定义一个类似Override的注解MyOverride.
首先我们在Android Studio中新建一个java Module;File->new ->new module 然后选java Module。然后我们在这个Module中新建一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyOverride {
}
Target和Retention的含义可以看之前的博客。然后我们再新建一个java Module,取名Processor;这个模块是编写注解处理的处理器。
对于注解处理器程序设计,我们需要把他当成一个独立程序来设计,同样需要考虑代码编写的哪些事项。
我们首先需要新建一个继承AbstractProcessor的类。
package com.example.lib; import com.example.annotation.MyOverride; import com.google.auto.service.AutoService; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; @AutoService(Processor.class) public class MyOverrideProcessor extends AbstractProcessor { private Messager messager; private Elements elementUtils; private Types typeUtils; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); elementUtils = processingEnv.getElementUtils(); typeUtils = processingEnv.getTypeUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); supportTypes.add(MyOverride.class.getCanonicalName()); return supportTypes; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { } }
首先我们来一步一步分析这个代码,我们先不考虑process函数的编写,后面再仔细讲解。
@AutoService(Processor.class)
这个注解是Google提供的一个辅助我们生成META-INF文件的注解,需要我们在gradle文件中引入implementation 'com.google.auto.service:auto-service:1.0-rc4'
,我们使用该注解后,会自动帮我们生成一个文件。如果不想用这个注解,自己其实也可以自己写。
生成的文件内容
如果自己写可以在main目录下新建res文件夹,目录下新建META-INF文件夹,目录下新建services文件夹,目录下新建javax.annotation.processing.Processor文件,然后将我们自定义注解处理器的全类名写到此文件:
首先我们看到程序重写了init方法,然后还获取了很多对象;Messager、 Elements 、Types ;这些主要是为了后续处理注解是方便使用。
我们可以使用ProcessingEnvironment获取一些实用类以及获取选项参数等:
方法 | 说明 |
---|---|
Elements getElementUtils() | 返回实现Elements接口的对象,用于操作元素的工具类。 |
Filer getFiler() | 返回实现Filer接口的对象,用于创建文件、类和辅助文件。 |
Messager getMessager() | 返回实现Messager接口的对象,用于报告错误信息、警告提醒。 |
Map getOptions() | 返回指定的参数选项。 |
Types getTypeUtils() | 返回实现Types接口的对象,用于操作类型的工具类。 |
我们可以吧java文件想象成一个通过很多积木堆积的东西,这些积木块就是一个个的元素。
类型 | 说明 |
---|---|
ExecutableElement | 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素。 |
PackageElement | 表示一个包程序元素。提供对有关包及其成员的信息的访问。 |
TypeElement | 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。 |
TypeParameterElement | 表示一般类、接口、方法或构造方法元素的形式类型参数。 |
VariableElement | 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。 |
如果我们要判断一个元素的类型,应该使用Element.getKind()方法配合ElementKind枚举类进行判断。尽量避免使用instanceof进行判断,因为比如TypeElement既表示类又表示一个接口,这样判断的结果可能不是你想要的。例如我们判断一个元素是不是一个类:
if (element instanceof TypeElement) { //错误,也有可能是一个接口
}
if (element.getKind() == ElementKind.CLASS) { //正确
//doSomething
}
下表为ElementKind枚举类中的部分常量,详细信息请查看官方文档。
类型 | 说明 |
---|---|
PACKAGE | 一个包。 |
ENUM | 一个枚举类型。 |
CLASS | 没有用更特殊的种类(如 ENUM)描述的类。 |
ANNOTATION_TYPE | 一个注解类型。 |
INTERFACE | 没有用更特殊的种类(如 ANNOTATION_TYPE)描述的接口。 |
ENUM_CONSTANT | 一个枚举常量。 |
FIELD | 没有用更特殊的种类(如 ENUM_CONSTANT)描述的字段。 |
PARAMETER | 方法或构造方法的参数。 |
LOCAL_VARIABLE | 局部变量。 |
METHOD | 一个方法。 |
CONSTRUCTOR | 一个构造方法。 |
TYPE_PARAMETER | 一个类型参数。 |
TypeMirror是一个接口,表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。以下类型接口全部继承自TypeMirror接口:
类型 | 说明 |
---|---|
ArrayType | 表示一个数组类型。多维数组类型被表示为组件类型也是数组类型的数组类型。 |
DeclaredType | 表示某一声明类型,是一个类 (class) 类型或接口 (interface) 类型。这包括参数化的类型(比如 java.util.Set)和原始类型。TypeElement 表示一个类或接口元素,而 DeclaredType 表示一个类或接口类型,后者将成为前者的一种使用(或调用)。 |
ErrorType | 表示无法正常建模的类或接口类型。 |
ExecutableType | 表示 executable 的类型。executable 是一个方法、构造方法或初始化程序。 |
NoType | 在实际类型不适合的地方使用的伪类型。 |
NullType | 表示 null 类型。 |
PrimitiveType | 表示一个基本类型。这些类型包括 boolean、byte、short、int、long、char、float 和 double。 |
ReferenceType | 表示一个引用类型。这些类型包括类和接口类型、数组类型、类型变量和 null 类型。 |
TypeVariable | 表示一个类型变量。 |
WildcardType | 表示通配符类型参数。 |
同样,如果我们想判断一个TypeMirror的类型,应该使用TypeMirror.getKind()方法配合TypeKind枚举类进行判断。尽量避免使用instanceof进行判断,因为比如DeclaredType既表示类 (class) 类型又表示接口 (interface) 类型,这样判断的结果可能不是你想要的。
TypeKind枚举类中的部分常量,详细信息请查看官方文档。
类型 | 说明 |
---|---|
BOOLEAN | 基本类型 boolean。 |
INT | 基本类型 int。 |
LONG | 基本类型 long。 |
FLOAT | 基本类型 float。 |
DOUBLE | 基本类型 double。 |
VOID | 对应于关键字 void 的伪类型。 |
NULL | null 类型。 |
ARRAY | 数组类型。 |
PACKAGE | 对应于包元素的伪类型。 |
EXECUTABLE | 方法、构造方法或初始化程序。 |
我们可以通过RoundEnvironment接口获取注解元素。process方法会提供一个实现RoundEnvironment接口的对象。
方法 | 说明 |
---|---|
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a) | 返回被指定注解类型注解的元素集合。 |
Set<? extends Element> getElementsAnnotatedWith(TypeElement a) | 返回被指定注解类型注解的元素集合。 |
processingOver() | 如果循环处理完成返回true,否则返回false。 |
我们了解了这些类的作用和,现在可以看看注解处理器程序。
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(MyOverride.class); for (Element element : elesWithBind) { checkAnnotationValid(element, MyOverride.class); ExecutableElement executableElement = (ExecutableElement) element; TypeElement classElement = (TypeElement) executableElement.getEnclosingElement(); if (!isOverriding(executableElement, classElement)) { error(executableElement, "该方法不是重写方法,不能使用MyOverride注解!"); } } return true; } public boolean isOverriding(ExecutableElement method, TypeElement base) { List<? extends TypeMirror> list = base.getInterfaces(); if (list != null && list.size() > 0) { for (TypeMirror type : list) { if (isOverridesInTypeMirror(method, base, type)) return true; } } TypeMirror superClass = base.getSuperclass(); return isOverridesInTypeMirror(method, base, superClass); } private boolean isOverridesInTypeMirror(ExecutableElement method, TypeElement base, TypeMirror type) { List<ExecutableElement> executableElements = ElementFilter.methodsIn(typeUtils.asElement(type).getEnclosedElements()); if (executableElements != null && executableElements.size() > 0) { for (ExecutableElement executableElement : executableElements) { if (elementUtils.overrides(method, executableElement, base)) { return true; } } } return false; } private boolean checkAnnotationValid(Element annotatedElement, Class clazz) { if (annotatedElement.getKind() != ElementKind.METHOD) { error(annotatedElement, "%s 的方法才能使用", clazz.getSimpleName()); return false; } return true; } private void error(Element element, String message, Object... args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message, element); }
对于有些关于注解的类的使用,不是很熟悉,我觉得可以看看这篇文档。Java example source code file
最后附上源码地址:
https://github.com/YZhongBin/MyOverride
自定义Java注解处理器
java 核心技术卷二
Java example source code file
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。