Android注解:自定义注解之源码注解

首先如果你对注解没有太多了解,建议先看一下我之前的两篇博客
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',我们使用该注解后,会自动帮我们生成一个文件。如果不想用这个注解,自己其实也可以自己写。
AutoService生成的文件
生成的文件内容
在这里插入图片描述
如果自己写可以在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 的伪类型。
NULLnull 类型。
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值