Android注解:自定义注解之源码注解
Android注解:自定义注解之源码注解
- 源码注解例1
- 源码注解例2
- 注解声明
- 注解处理程序编写
- 基本数据类型
- 工具类类型
- 元素类型
- 类型
- 获取注解元素
- 注解处理器
- 参考资料
首先如果你对注解没有太多了解,建议先看一下我之前的两篇博客
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
更多相关文章
- Android SDK更新 Connection to http://dl-ssl.google.com refus
- RecyclerView 各种相关问题解决方法
- Android基本控件常用属性及方法
- 安卓布局属性代码中文注解
- 【Android常用控件】EditText常用属性【二】:为文本输入框指定软
- 安卓textview edittext 中inputType的类型
- Android中EditText的inputType属性(键盘类型)
- Android EditText身份证等类型
- Android中一个Activity多个intent-filter的调用方法