Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

「Android」Android开发你需要知道的注解(Annotation) #30

Open
or0fun opened this issue Jun 9, 2017 · 3 comments
Open

「Android」Android开发你需要知道的注解(Annotation) #30

or0fun opened this issue Jun 9, 2017 · 3 comments

Comments

@or0fun
Copy link

or0fun commented Jun 9, 2017

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

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

【说在前面的话】

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

#一、什么是注解

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

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

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

其实大家都是用过注解的,只是可能没有过深入了解它的原理和作用,比如肯定见过@Override ,@Deprecated等。

##1、注解的作用

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

##2、注解都有哪些
平时我们使用的注解有来自JDK里包含的,也有Android SDK里包含的,也可以自定义。
2.1、JDK定义的元注解

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

二、自定义注解

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

1、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);
}

2、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());  
}  

所做的操作都是通过反射获取对应元素,再获取元素上面的注解,最后得到注解的属性值。因为涉及到反射,所以运行时注解的效率多少会受到影响,现在很多的开源项目使用的是编译时注解。

3、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

参考链接:
http://www.jb51.net/article/80240.htm
http://blog.csdn.net/github_35180164/article/details/52121038
http://blog.csdn.net/github_35180164/article/details/52107204
https://www.zhihu.com/question/36486629
https://github.com/alibaba/ARouter/
http://blog.csdn.net/asce1885/article/details/52878076

@isswu
Copy link

isswu commented Jun 18, 2017

That's cool!

@wangankang
Copy link

cool

@jessandroid
Copy link

不错的文章,感谢分享

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants