「Android」Android开发你须要知道的注解(Annotation)

本文来自尚妆Android团队路飞
发表于尚妆github博客,欢迎订阅!javascript

  • 1、什么是注解
    • 一、注解的做用
    • 二、注解都有哪些
  • 2、自定义注解
    • 一、RetentionPolicy.SOURCE
    • 二、RetentionPolicy.RUNTIME
    • 三、RetentionPolicy.CLASS

【说在前面的话】java

  • 要想看懂不少开源库,如Arouter, dagger,Butter Knife等,不得不先看懂注解;
  • 想更好地提高开发效率和代码质量,注解能够帮上很大的忙;

#1、什么是注解android

java.lang.annotation,接口 Annotation,在JDK5.0及之后版本引入。
注解是代码里的特殊标记,这些标记能够在编译、类加载、运行时被读取,并执行相应的处理。经过使用Annotation,开发人员能够在不改变原有逻辑的状况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具能够经过这些补充信息进行验证、处理或者进行部署。git

在运行时读取须要使用Java反射机制进行处理。github

Annotation不能运行,它只有成员变量,没有方法。Annotation跟public、final等修饰符的地位同样,都是程序元素的一部分,Annotation不能做为一个程序元素使用。api

其实你们都是用过注解的,只是可能没有过深刻了解它的原理和做用,好比确定见过@Override,@Deprecated等。数组

##一、注解的做用app

注解将一些原本重复性的工做,变成程序自动完成,简化和自动化该过程。好比用于生成Java doc,好比编译时进行格式检查,好比自动生成代码等,用于提高软件的质量和提升软件的生产效率。jvm

##二、注解都有哪些
平时咱们使用的注解有来自JDK里包含的,也有Android SDK里包含的,也能够自定义。
2.一、JDK定义的元注解maven

Java提供了四种元注解,专门负责新注解的建立工做,即注解其余注解。

  • @Target
    定义了Annotation所修饰的对象范围,取值: 

    • ElementType.CONSTRUCTOR:用于描述构造器
    • ElementType.FIELD:用于描述域
    • ElementType.LOCAL_VARIABLE:用于描述局部变量
    • ElementType.METHOD:用于描述方法
    • ElementType.PACKAGE:用于描述包
    • ElementType.PARAMETER:用于描述参数
    • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Retention
    定义了该Annotation被保留的时间长短,取值:
     - RetentionPoicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于作一些检查性的操做,好比 @Override@SuppressWarnings
     - RetentionPoicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操做,好比生成一些辅助代码(如 ButterKnife
     - RetentionPoicy.RUNTIME:注解不只被保存到class文件中,jvm加载class文件以后,仍然存在;用于在运行时去动态获取注解信息。  

  • @Documented
    标记注解,用于描述其它类型的注解应该被做为被标注的程序成员的公共API,所以能够被例如javadoc此类的工具文档化,不用赋值。

  • @Inherited
    标记注解,容许子类继承父类的注解。 这里一开始有点理解不了,须要断句一下,容许子类继承父类的注解。示例:
    ```
    Target(value = ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface Sample {
    public String name() default "";
    }

@Sample
class Test{
}

class Test2 extents Test{
}

这样类Test2其实也有注解@Sample  。


>若是成员名称是value,在赋值过程当中能够简写。若是成员类型为数组,可是只赋值一个元素,则也能够简写。
>示例如下三个写法都是等价的。

正常写法复制代码

@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}

省略value的写法(只有成员名称是value时才能省略)复制代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}

成员类型是数组,只赋值一个元素的简写复制代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

** 2.2 JDK内置的其余注解 **

`@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface、@Resources`等。

** 2.3 Android SDK内置的注解 **

Android SDK 内置的注解都在包com.android.support:support-annotations里,下面以'com.android.support:support-annotations:25.2.0'为例

- 资源引用限制类:用于限制参数必须为对应的资源类型
`@AnimRes @AnyRes @ArrayRes @AttrRes @BoolRes @ColorRes等`
- 线程执行限制类:用于限制方法或者类必须在指定的线程执行
`@AnyThread @BinderThread @MainThread @UiThread @WorkerThread`
- 参数为空性限制类:用于限制参数是否能够为空
`@NonNull @Nullable`
- 类型范围限制类:用于限制标注值的值范围
`@FloatRang @IntRange`
- 类型定义类:用于限制定义的注解的取值集合
`@IntDef @StringDef`
- 其余的功能性注解:
`@CallSuper @CheckResult @ColorInt @Dimension @Keep @Px @RequiresApi @RequiresPermission @RestrictTo @Size @VisibleForTesting`

# 2、自定义注解

使用收益最大的,仍是须要根据自身需求自定义注解。下面依次介绍三种类型的注解自定义示例:

## 一、RetentionPolicy.SOURCE
通常函数的参数值有限定的状况,好比View.setVisibility 的参数就有限定,能够看到View.class源码里

除了`IntDef`,还有`StringDef`复制代码

@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}

public static final int VISIBLE = 0x00000000;

public static final int INVISIBLE = 0x00000004;

public static final int GONE = 0x00000008;

public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}

## 二、RetentionPolicy.RUNTIME
运行时注解的定义以下:复制代码

// 适用类、接口(包括注解类型)或枚举
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.TYPE)
public @interface ClassInfo {
String value();
}
// 适用field属性,也包括enum常量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
int[] value();
}
// 适用方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodInfo {
String name() default "long";
int age() default 27;
}

定义一个测试类来使用这些注解:复制代码

/**

  • 测试运行时注解
    */
    @ClassInfo("Test Class")
    public class TestRuntimeAnnotation {

    @FieldInfo(value = {1, 2})
    public String fieldInfo = "FiledInfo";

    @MethodInfo(name = "BlueBird")
    public static String getMethodInfo() {

    return return fieldInfo;  复制代码

    }
    }

    使用注解:复制代码

    /**

  • 测试运行时注解
    */
    private void _testRuntimeAnnotation() {
    StringBuffer sb = new StringBuffer();
    Class<?> cls = TestRuntimeAnnotation.class;
    Constructor<?>[] constructors = cls.getConstructors();
    // 获取指定类型的注解
    sb.append("Class注解:").append("\n");
    ClassInfo classInfo = cls.getAnnotation(ClassInfo.class);
    if (classInfo != null) {

    sb.append(cls.getSimpleName()).append("\n");  
     sb.append("注解值: ").append(classInfo.value()).append("\n\n");  复制代码

    }

    sb.append("Field注解:").append("\n");
    Field[] fields = cls.getDeclaredFields();
    for (Field field : fields) {

    FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);  
     if (fieldInfo != null) {  
         sb.append(field.getName()).append("\n");  
         sb.append("注解值: ").append(Arrays.toString(fieldInfo.value())).append("\n\n");  
     }  复制代码

    }

    sb.append("Method注解:").append("\n");
    Method[] methods = cls.getDeclaredMethods();
    for (Method method : methods) {

    MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);  
     if (methodInfo != null) {  
         sb.append(Modifier.toString(method.getModifiers())).append(" ")  
                 .append(method.getName()).append("\n");  
         sb.append("注解值: ").append("\n");  
         sb.append("name: ").append(methodInfo.name()).append("\n");  
         sb.append("age: ").append(methodInfo.age()).append("\n");  
     }  复制代码

    }

    System.out.print(sb.toString());
    }
    ```
    所作的操做都是经过反射获取对应元素,再获取元素上面的注解,最后获得注解的属性值。由于涉及到反射,因此运行时注解的效率多少会受到影响,如今不少的开源项目使用的是编译时注解。

三、RetentionPolicy.CLASS

3.1 添加依赖

若是Gradle 插件是2.2以上的话,不须要添加如下android-apt依赖。

classpath 'com.android.tools.build:gradle:2.2.1'复制代码

在整个工程的 build.gradle 中添加android-apt的依赖

buildscript {  
    repositories {  
        jcenter()  
        mavenCentral()  // add 
    }  
    dependencies {  
        classpath 'com.android.tools.build:gradle:2.1.2' //2.2以上无需添加apt依赖 
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add 
    }  
}复制代码

android-apt
android-apt 是一个Gradle插件,协助Android Studio 处理annotation processors, 它有两个目的:

  • 容许配置只在编译时做为注解处理器的依赖,而不添加到最后的APK或library
  • 设置源路径,使注解处理器生成的代码能被Android Studio正确的引用

伴随着 Android Gradle 插件 2.2 版本的发布,近期 android-apt 做者在官网发表声明证明了后续将不会继续维护 android-apt,并推荐你们使用 Android 官方插件提供的相同能力。也就是说,大约三年前推出的 android-apt 即将告别开发者,退出历史舞台,Android Gradle 插件提供了名为 annotationProcessor 的功能来彻底代替 android-apt。

3.2 定义要使用的注解
建一个Java库来专门放注解,库名为:annotations
定义注解

@Retention(RetentionPolicy.CLASS)  
@Target(ElementType.TYPE)  
public @interface MyAnnotation {  
    String value();  
}复制代码

3.3 定义注解处理器
另外建一个Java库工程,库名为:processors
这里必须为Java库,否则会找不到javax包下的相关资源

build.gradle 要依赖如下:

compile 'com.google.auto.service:auto-service:1.0-rc2'
 compile 'com.squareup:javapoet:1.7.0'

compile(project(':annotations'))复制代码

其中,
auto-service 自动用于在 META-INF/services 目录文件夹下建立 javax.annotation.processing.Processor 文件;
javapoet用于产生 .java 源文件的辅助库,它能够很方便地帮助咱们生成须要的.java 源文件

示例:

package com.example;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
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.Modifier;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // Filer是个接口,支持经过注解处理器建立新文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement element : annotations) {
            //新建文件
            if (element.getQualifiedName().toString().equals(MyAnnotation.class.getCanonicalName())) {
                // 建立main方法
                MethodSpec main = MethodSpec.methodBuilder("main")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .returns(void.class)
                        .addParameter(String[].class, "args")
                        .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                        .build();
                // 建立HelloWorld类
                TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                        .addMethod(main)
                        .build();

                try {
                    // 生成 com.example.HelloWorld.java
                    JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                            .addFileComment(" This codes are generated automatically. Do not modify!")
                            .build();
                    // 生成文件
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //在Gradle console 打印日志
        for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
            System.out.println("------------------------------");
            // 判断元素的类型为Class
            if (element.getKind() == ElementKind.CLASS) {
                // 显示转换元素类型
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                // 输出注解属性值System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());
            }
            System.out.println("------------------------------");
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add(MyAnnotation.class.getCanonicalName());
        return annotataions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}复制代码

3.4 在代码中使用定义的注解
须要依赖上面的两个java库annotations和processors

import com.example.MyAnnotation;

@MyAnnotation("test")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}复制代码

编译后就会生成指定包名的指定文件,如图:

Alt text

3.5 注解处理器的辅助接口
在自定义注解处理器的初始化接口,能够获取到如下4个辅助接口:

public class MyProcessor extends AbstractProcessor {  

    private Types typeUtils;  
    private Elements elementUtils;  
    private Filer filer;  
    private Messager messager;  

    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);  
        typeUtils = processingEnv.getTypeUtils();  
        elementUtils = processingEnv.getElementUtils();  
        filer = processingEnv.getFiler();  
        messager = processingEnv.getMessager();  
    }  
}复制代码

Types: Types是一个用来处理TypeMirror的工具
Elements: Elements是一个用来处理Element的工具
Filer: 通常咱们会用它配合JavaPoet来生成咱们须要的.java文件
Messager: Messager提供给注解处理器一个报告错误、警告以及提示信息的途径

3.5 带有注解的库提供给第三方

如下例子默认用gradle插件2.2以上,再也不使用apt

通常使用编译时注解的库,都会有三个module:

  • 定义注解的module , java库,xxxx-annotations
  • 实现注解器的module, java库,xxxx-compiler
  • 提供对外接口的module, android库,xxxx-api

module xxxx-api的依赖这么写:
dependencies { annotationProcessor 'xxxx-compiler:1.0.0' compile ' xxxx-annotations:1.0.0' //....others }

而后第三方使用时,能够像以下这样依赖:

dependencies {
    ...
    compile 'com.google.dagger:dagger:2.9'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.9'
}复制代码

sample project见AnnotationSample

参考连接:
www.jb51.net/article/802…
blog.csdn.net/github_3518…
blog.csdn.net/github_3518…
www.zhihu.com/question/36…
github.com/alibaba/ARo…
blog.csdn.net/asce1885/ar…

相关文章
相关标签/搜索