美文网首页Android面试相关
「Android」Android开发你需要知道的注解(Annot

「Android」Android开发你需要知道的注解(Annot

作者: 尚妆产品技术刊读 | 来源:发表于2017-06-15 21:07 被阅读72次

    本文来自尚妆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

    相关文章

      网友评论

        本文标题:「Android」Android开发你需要知道的注解(Annot

        本文链接:https://www.haomeiwen.com/subject/rwiuqxtx.html