首先如果你对注解没有太多了解,建议先看一下我之前的两篇博客
Android注解:Java注解
Android注解:Android 常用注解
这两篇博客都详细介绍了关于Android注解的一些基础知识。这是Android自定义注解的第一篇,源码注解。Android注解类型可以分为三种,源码注解,字节码注解,运行时注解;每一种注解都有其特定的用途。我认为要想真正透彻的理解这三种注解,我们必须充分的了解其适应的场景。对于源码注解,只会在源码上保留,所以源码注解最适合的场景就是做代码正确性的检测。
源码注解例1
前面瞎扯了这么多,我们先看一个非常简单的例子。
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变量赋予了其他值,也不会影响编译,注解只是检查了一下代码,给出对应的提示。
源码注解例2
注解声明
上面的例子非常简单,通过@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