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',我们使用该注解后,会自动帮我们生成一个文件。如果不想用这个注解,自己其实也可以自己写。
Android注解:自定义注解之源码注解_第1张图片
生成的文件内容
Android注解:自定义注解之源码注解_第2张图片
如果自己写可以在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

更多相关文章

  1. Android SDK更新 Connection to http://dl-ssl.google.com refus
  2. RecyclerView 各种相关问题解决方法
  3. Android基本控件常用属性及方法
  4. 安卓布局属性代码中文注解
  5. 【Android常用控件】EditText常用属性【二】:为文本输入框指定软
  6. 安卓textview edittext 中inputType的类型
  7. Android中EditText的inputType属性(键盘类型)
  8. Android EditText身份证等类型
  9. Android中一个Activity多个intent-filter的调用方法

随机推荐

  1. Android源码快速查找文件、搜索字符串和
  2. 使用SQLite中自带的API操作SQLite数据库
  3. 开发应用程序的Android(安卓)- 入门
  4. Android 动态加载 ListView 实现
  5. Android的进程
  6. 87、android处理耗时任务
  7. android 视图设置多个setTag数据
  8. 转载:Android判断当前应用是否在前台运行
  9. android生成R.java文件
  10. 关于AlertDialog中EditText不能弹出输入