Android開發你需要知道的注解(Annotation)

rcxv2526 7年前發布 | 27K 次閱讀 安卓開發 Java Gradle Java開發

一、什么是注解

  • 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);
    }
}

編譯后就會生成指定包名的指定文件,如圖:

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/8...

http://blog.csdn.net/github_3...

http://blog.csdn.net/github_3...

https://www.zhihu.com/questio...

https://github.com/alibaba/AR...

http://blog.csdn.net/asce1885...

 

來自: github.com/ShowJoy-com/showjoy-blog

 

 本文由用戶 rcxv2526 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!