Android注解

本文目錄:

  • 一. 注解概念和介紹
  • 二. 注解的語法
  • 三. 基本注解——五大元注解
  • 四. AnnotatedElement 接口
  • 五. AbstractProcessor (注解處理器)
  • 六. javapoet(第三方庫,生成.java源文件)
  • 七. APT技術
  • 八. demo實戰(zhàn)

一.注解概念和介紹

  • 注解(annotation)也叫元數(shù)據(jù)。JDK1.5版本后引入的特性,它可以聲明在包,類,字段,方法,局部變量,方法參數(shù)等的前面,用來對這些元素進行說明和注釋。

二.注解的語法

  • 注解通過@interface關鍵字來定義。

三.基本注解——五大元注解

@Retention, @Documented, @Target, @Inherited, @Repeatable

元注解是指可以注解到注解上的注解,即使用了@Target(ElementType.ANNOTATION_TYPE)。
五大元注解 介紹
@Retention 指注解保留的時長
@Documented 跟文檔相關,其作用是能夠將注解中的元素包含到javadoc中去
@Target 指定注解可作用的目標
@Inherited 父類被(Inherited修飾的注解)注解修飾,它的子類如果沒有任何注解修飾,就會繼承父類的這個注解(看下面例子更好理解)
@Repeatable 表明標記的注解可以多次應用于相同的聲明或類型

注意:@Repeatable JDK1.8以上才有,Android系統(tǒng)7.0(對應SDK24)以上才有


  • 3.1@Retention:中文保留的意思,指注解保留的時長。

三種取值如下:

@Retention三種取值 介紹 用途
RetentionPolicy.SOURCE 注解只在源碼階段保留,批注將被編譯器丟棄。 主要用于提示開發(fā)者
RetentionPolicy.CLASS 注解保留到編譯期(注解將由編譯器記錄在類文件中),但在虛擬機VM運行時不需要保留他們。這是默認的。 主要用于自動生成代碼
RetentionPolicy.RUNTIME 注解會保留到程序運行時(注解由編譯器記錄在類文件中,并在運行時由虛擬機VM保留),因此可以通過反射方式讀取它們。 主要用于自動注入

@Retention 源碼:

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

@RetentionPolicy 源碼

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

  • 3.2@ Documented: 中文文獻的意思,可看出跟文檔相關,其作用是能夠將注解中的元素包含到javadoc中去。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

  • 3.3@Target: 指定注解可作用的目標

10種取值如下:

@ Target十種取值 作用
1. ElementType.TYPE 可作用在類、接口、枚舉上
2. ElementType.FIELD 可作用在屬性上
3. ElementType.METHOD 可作用在方法上
4. ElementType.PARAMETER 可作用在方法參數(shù)上
5. ElementType.CONSTRUCTOR 可作用在構造方法上
6. ElementType.LOCAL_VARIABLE 可作用在局部變量上,例如方法中定義的變量
7.ElementType.ANNOTATION_TYPE 可以作用在注解上
8.ElementType.PACKAGE 可作用在包上
9. ElementType.TYPE_PARAMETER JDK1.8才有
10. ElementType. TYPE_USE JDK1.8才有

@Target源碼

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

上面看@Target源碼可知其取值為一個數(shù)組,舉兩個例子,一個取值和兩個取值以上:@Target(ElementType.ANNOTATION_TYPE),@Target({ElementType.ANNOTATION_TYPE,ElementType.PACKAGE})

ElementType 源碼

public enum ElementType {
   TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
}

  • 3.4@Inherited: 繼承的意思,但并不是注解本身可被繼承,而是指一個父類SuperClass被該類注解修飾,那么它的子類SubClass如果沒有任何注解修飾,就會繼承父類的這個注解。

例子:

@Inherited
@Target(ElementType.Type)
@Retention(RetentionPolicy.RUNTIME)
public @interface Money{}

@Money
public class Father{}

public class Son extends Father {}

解釋:注解Money被@Inherited修飾,F(xiàn)ather被Money修飾,Son 繼承Father(Son 上又無其他注解),那么Son 就會擁有Money這個注解。


  • 3.5@ Repeatable,這個詞是可重復的意思,它是java1.8引入的,算一個新特性。

什么樣的注解可以多次應用來呢,通常是注解可以取多個值

例子:

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)
public @Interface Teachers{
    Teacher[] value();
}

@Repeatable(Teachers.class)
public @Interface Teacher{
    String role() default ""
}

@Teacher(role="Chinese")
@Teacher(role="Math")
@Teacher(role="English")
public class Me {}

public static void main(String[] args) {
    if(Me .class.isAnnotationPresent(Teachers.class)) {
        Teachers teaches=Man.class.getAnnotation(Teachers.class);
        for(Teacher teacher : teachers.value()){
            System.out.println(teacher.role());
        }
 }

例子解析:@Teacher被@Repeatable修飾,所以Teacher可以多次作用在同一個對象Me上,而Repeatable接收一個參數(shù),這個參數(shù)是個容器注解,用來存放多個@Person。


四. AnnotatedElement 接口

  • 該接口表示此虛擬機VM中當前正在運行的程序的帶注釋元素。
  • 所有實現(xiàn)了這個接口的“元素”都是可以“被注解的元素”。
  • 使用這個接口中聲明的方法可以讀取(通過Java的反射機制)“被注解元素”的注解。 即該接口允許以反射方式讀取注解。
  • 該接口中方法返回的所有注釋都是不可變的和可序列化的。 調用方可以修改此接口的方法返回的數(shù)組,而不會影響返回給其他調用方的數(shù)組。

1.AnnotatedElement 方法

AnnotatedElement 方法 介紹
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定類型的注解出現(xiàn)在當前元素上,則返回true,否則將返回false。這種方法主要是為了方便地訪問一些已知的注解。
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 如果在當前元素上存在參數(shù)所指定類型(annotationClass)的注解,則返回對應的注解,否則將返回null。
Annotation[] getAnnotations() 返回在這個元素上的所有注解。如果該元素沒有注釋,則返回值是長度為0的數(shù)組。
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 返回與該元素相關聯(lián)的注解。如果沒有與此元素相關聯(lián)的注解,則返回值是長度為0的數(shù)組。這個方法與getAnnotation(Class)的區(qū)別在于,該方法檢測其參數(shù)是否為可重復的注解類型,如果是,則嘗試通過“l(fā)ooking through”容器注解來查找該類型的一個或多個注解。
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) 如果參數(shù)中所指定類型的注解是直接存在于當前元素上的,則返回對應的注解,否則將返回null。這個方法忽略了繼承的注解。(如果沒有直接在此元素上顯示注釋,則返回null。)
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) 如果參數(shù)中所指定類型的注解是直接存在或間接存在于當前元素上的,則返回對應的注解。這種方法忽略了繼承的注釋。如果沒有直接或間接地存在于此元素上的指定注解,則返回值是長度為0的數(shù)組。這個方法和getDeclaredAnnotation(Class)的區(qū)別在于,這個方法檢測它的參數(shù)是否為可重復的注釋類型(JLS 9.6),如果是,則嘗試通過“l(fā)ooking through”容器注解來查找該類型的一個或多個注解。
Annotation[] getDeclaredAnnotations() 返回直接出現(xiàn)在這個元素上的注解。這種方法忽略了繼承的注解。如果在此元素上沒有直接存在的注解,則返回值是長度為0的數(shù)組。

2.AnnotatedElement 的實現(xiàn)類

AnnotatedElement的實現(xiàn)類 介紹 備注
Class 類,天天打交道的 \
Package \
Parameter 參數(shù),主要指方法或函數(shù)的參數(shù),其實是這些參數(shù)的類型 JDK1.8才有
AccessibleObject 是Field、Method 和 Constructor 對象的基類。所以可訪問對象,如:方法、構造器、屬性等 \
Field 屬性,類中屬性的類型 繼承于AccessibleObject
Executable 可執(zhí)行的,如構造器和方法 繼承于AccessibleObject;JDK1.8才有
Constructor 構造器 繼承于Executable
Method 方法 繼承于Executable

五. AbstractProcessor (注解處理器)

  • 5.1AbstractProcessor介紹

注解處理器一共有七個方法,我們一般重寫四個方法

public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    @Override
    public SourceVersion getSupportedSourceVersion() { }

}
5.1.1 init(ProcessingEnvironment env):

每一個注解處理器類都必須有一個空的構造函數(shù)。然而,這里有一個特殊的init()方法,它會被注解處理工具調用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類Elements, Types和Filer。

5.1.2 process(Set<? extends TypeElement> annotations, RoundEnvironment env):

這相當于每個處理器的主函數(shù)main()。你在這里寫你的掃描、評估和處理注解的代碼,以及生成Java文件。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。

5.1.3 getSupportedAnnotationTypes():

這里我們必須指定,這個注解處理器是注冊給哪個注解的。它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱。換句話說,我們在這里定義我們的注解處理器注冊到哪些注解上。

5.1.4 getSupportedSourceVersion():

用來指定使用的Java版本。通常這里返回SourceVersion.latestSupported()。

  • 5.2 AbstractProcessor使用(Android需新建Java Library)

5.2.1 寫一個注解
5.2.2 寫一個類繼承于AbstractProcessor,然后重寫上述四個方法
5.2.3 添加SPI配置文件(手動或者使用google提供的auto-service庫)

手動配置:在該Java Library下的main目錄下創(chuàng)建resources/META-INF/services目錄,然后在 META-INF/services 目錄文件夾下創(chuàng)建javax.annotation.processing.Processor 文件;在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱,包括包路徑;包名+自定義處理器文件名)

自定義AbstractProcessor是APT技術的頂梁柱,這里介紹了基本的知識,更多的請看相關的APT知識。

六. javapoet(square公司出品的第三方庫,生成java文件)

  • 6.1 javapoet介紹:

JavaPoet是用于生成.java源文件的Java API。由square公司出品的第三方庫。
在執(zhí)行諸如注解處理或與元數(shù)據(jù)文件(例如,數(shù)據(jù)庫模式,協(xié)議格式)交互之類的操作時,源文件的生成非常有用。 通過生成代碼,無需編寫樣板文件,同時還保留了元數(shù)據(jù)的唯一真實來源。

  • 6.2 javapoet使用(Android需新建Java Library)

javapoet在AndroidStudio中不能直接使用,需要新建一個Java Library。Java Library中依賴compile 'com.squareup:javapoet:1.12.1'
下面例子MyJavapoet是寫在Java Library的,運行MyJavapoet,就可在目錄下看到生成的名為HelloWorld的Java文件了

public class MyJavapoet {
    public static void main(String[] args) {
        generateJava();
    }

    private static void generateJava() {
        //1. 生成一個字段
        FieldSpec fieldSpec = FieldSpec.builder(String.class, "var", Modifier.PUBLIC).build();

        //2. 生成一個方法
        MethodSpec main = MethodSpec.methodBuilder("main")//設置方法名稱
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//設置方法名稱
                .returns(void.class)//添加返回值
                .addParameter(String[].class, "args")//添加參數(shù)
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")//添加代碼語句 (結束語句的分號不需要, 注意與CodeBlock的區(qū)別)
                .build();

        //3. 生成類型(enum/class/annotation/interface)
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .build();

        //4. 構建Java源文件
        JavaFile javaFile = JavaFile.builder("annotation.hsj.pri", helloWorld)
                .build();

        //5.生成到當前module的源文件目錄下
        try {
            javaFile.writeTo(System.out);
            //輸出到和用例程序相同的源碼目錄下,targetDirectory后面會自動添加以上述包名為文件名的目錄
            String targetDirectory = "annotationlib/src/main/java/";
            File dir = new File(targetDirectory);
            if (!dir.exists()) dir.mkdirs();
            javaFile.writeTo(dir); //JavaFile.write(), 參數(shù)為源碼生成目錄(源碼的classpath目錄)
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

javapoet的詳細信息請見javapoet的GitHub:https://github.com/square/javapoet

七. APT技術

APT技術主要是通過編譯期解析注解,并且生成java代碼的一種技術,熟練掌握了該技術,對于ButterKnife、Dagger、EventBus里面的注解就非常容易理解了。
其三大基礎為:

  1. 一個注解(@Retention(RetentionPolicy.CLASS));
  2. 一個自定義的AbstractProcessor(注解解析器);
  3. 一個生成Java代碼的Javapoet技術。

這里限于篇幅,但又在自定義注解里非常重要的一個綜合知識面,建議大家先看完 八實戰(zhàn)demo 最后再來看APT技術。這里我就不寫了,給大家推薦一篇專門講APT的文章:http://www.itdecent.cn/p/7af58e8e3e18

八. 實戰(zhàn)demo

8.1 按作用域上分(幾種常見的)

8.1.1 作用在屬性上(@Target(ElementType.FIELD)),如成員變量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyFieldAnnotation {
   @IdRes int id() default -1;
}

---------------------------------------------------------------------------------------------

public class FieldActivity extends AppCompatActivity {
   @FieldAnnotation(id = R.id.tv)
   TextView textView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_field);
       field();
   }

   private void field() {
       Field[] fields = this.getClass().getDeclaredFields();
       //通過該方法設置所有的字段都可訪問,否則即使是反射,也不能訪問private修飾的字段
       Field.setAccessible(fields, true);
       for (Field field : fields) {
           if (field.isAnnotationPresent(FieldAnnotation.class)) {
               MyFieldAnnotation fieldAnnotation = field.getAnnotation(MyFieldAnnotation.class);
               int id = fieldAnnotation.id();
               if (id == -1) continue;
               View view = this.findViewById(id);
               Class aClass = field.getType();
               try {
                   field.set(this, aClass.cast(view));
               } catch (IllegalAccessException e) {
                   e.printStackTrace();
               }
           }
       }
       textView.setText("提莫隊長");
   }
}

例子分析:

  • 作用在Field上即@Target(ElementType.FIELD)
  • 這里是實現(xiàn)一個跟ButterKnife類似的功能,即自動注入功能,無需手動調用findViewById。
    第一步:定義一個注解。我們這里定義了一個運行時注解,其取值id我們作為findVIewById的id值
    第二步:定義注解解析工具。例子里是通過field方法代替解析工具的。通過Field[]獲取Activity的field成員變量。然后 AccessibleObject.setAccessible()設置所有字段都可以訪問,這里AccessibleObject也可以改為Field,AccessibleObject是Field的父類。然后循環(huán)遍歷Field[]得到每個Field,判斷指定的注解MyInjectViewAnnotation是否在當前Field元素上,如果在,則取出當前Field元素上的MyInjectViewAnnotation注解,然后得到注解上的id值,然后就可以進行findViewById操作了。
    第三步:使用注解。例子里申明一個Field成員變量TextView,將注解作用于 TextView 上。
8.1.2 作用在方法上(@Target(ElementType.METHOD))
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyMethodAnnotation {
   String name() default "";

   String sex() default "";

   int age() default 0;
}

-----------------------------------------------------------------------------------------------------------------

public class MethodActivity extends AppCompatActivity {
   private TextView textView;
   private StringBuffer stringBuffer = new StringBuffer();

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_method);
       textView = findViewById(R.id.tv_method);
       method();
   }

   @MethodAnnotation(name = "德瑪", sex = "男", age = 18)
   public void method() {
       Method[] methods = this.getClass().getMethods();
       AccessibleObject.setAccessible(methods, true);
       for (Method method : methods) {
           if (method.isAnnotationPresent(MethodAnnotation.class)) {
               MyMethodAnnotation methodAnnotation = method.getAnnotation(MyMethodAnnotation.class);
               stringBuffer.append("姓名:" + methodAnnotation.name() + methodAnnotation.age() + " 性別:" + "  年齡:" + methodAnnotation.sex());
               Log.d("MethodActivity-->", "姓名:" + methodAnnotation.name() + "  年齡:" + methodAnnotation.age() + " 性別:" + methodAnnotation.sex());
           }
       }
       textView.setText(stringBuffer);
   }
}

疑問?這里注解所作用的方法必須為public,即使設置AccessibleObject.setAccessible(methods, true);還是無法訪問private修飾的方法,這里我不太明白,有小伙伴知道的可以教教我哦。

在8.1.3 作用在方法參數(shù)上(@Target(ElementType.PARAMETER))
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
@StringDef({ParameterAnnotationStringA.PRIMARY_SCHOOL, ParameterAnnotationStringA.JUNIOR_SCHOOL, >ParameterAnnotationStringA.SENIOR_SCHOOL})
public @interface ParameterAnnotationStringA {
   String PRIMARY_SCHOOL = "小學";
   String JUNIOR_SCHOOL = "初中";
   String SENIOR_SCHOOL = "高中";
}
---------------------------------------------------------------------------------------------------------------
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
@StringDef({"小學","初中","高中"})
public @interface ParameterAnnotationStringB {

}

---------------------------------------------------------------------------------------------------------------
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
@IntRange(from = 5, to = 20)
public @interface ParameterAnnotationIntRange {

}

---------------------------------------------------------------------------------------------------------------

public class ParameterActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_parameter);

       parameterStringA("小學");//報錯:即使PRIMARY_SCHOOL也是小學,但這里必須使用PRIMARY_SCHOOL和下條一樣
       parameterStringA(ParameterAnnotationStringA.PRIMARY_SCHOOL);

       parameterStringB("小學");
       parameterStringB("小學生");//報錯:因為ParameterAnnotationStringB沒有小學生
       parameterStringB(ParameterAnnotationStringA.PRIMARY_SCHOOL);

       parameterIntRange(15);
       parameterIntRange(30);//報錯:因為ParameterAnnotationIntRange的IntRange是從5到20
   }

   private void parameterStringA(@ParameterAnnotationStringA String schoolName) {
       Log.d("ParameterActivity-->", "parameterStringA():" + "schoolName " + schoolName);
   }

   private void parameterStringB(@ParameterAnnotationStringB String schoolName) {
       Log.d("ParameterActivity-->", "parameterStringB():" + "schoolName " + schoolName);
   }

   private void parameterIntRange(@ParameterAnnotationIntRange int age) {
       Log.d("ParameterActivity-->", "parameterIntRange():" + "age " + age);
   }
}

例子解析:

  • 打印結果依次為:小學;小學;小學;小學生;小學;15;30 ;也就是這里寫代碼代碼的時候會提示警告信息,但編譯和運行不會出現(xiàn)任何錯誤。
  • 作用在方法參數(shù)上即@Target(ElementType.PARAMETER)。案例中有寫@StringDef,@IntRange,這是對方法參數(shù)進行約束。
  • 如果注解中有寫了變量并賦值,拿ParameterAnnotationStringA注解舉例,那么就參數(shù)必須寫StringDef里的ParameterAnnotationStringA.PRIMARY_SCHOOL等,而不能寫ParameterAnnotationStringA.PRIMARY_SCHOOL的值“小學”。
  • 但是反過來,卻可以使用ParameterAnnotationStringA.PRIMARY_SCHOOL,如例子里的parameterStringA(ParameterAnnotationStringA.PRIMARY_SCHOOL);
  • 對第三點和第四點進行總結,即可以使用“小學”的一定可以使用ParameterAnnotationStringA.PRIMARY_SCHOOL,但可以使用ParameterAnnotationStringA.PRIMARY_SCHOOL的不一定可以使用“小學”,即變量名稱大于變量的值。這個跑一下demo更直觀更容易理解。
  • 作用在方法參數(shù)上,這個可以常用于對參數(shù)進行約束,比如開發(fā)中會常用到生產(chǎn)環(huán)境地址,測試環(huán)境地址以及其他地址,為了開發(fā)者更統(tǒng)一規(guī)范避免地址錯誤,就可以對這些地址進行注解來進行約束。

8.2 按注解保留時長分(源碼期,編譯期,運行期)

8.2.1 源碼期@Retention(RetentionPolicy.SOURCE)

demo和8.1.3一樣

8.2.2 編譯期@Retention(RetentionPolicy.CLASS)

編譯期很重要的一個是APT技術,具體請詳見上面 七.APT技術

8.2.3 運行期@Retention(RetentionPolicy.RUNTIME)

demo和8.1.1一樣
運行時注解主要通過反射進行解析,代碼運行過程中,通過反射我們可以知道哪些屬性、方法使用了該注解,并且可以獲取注解中的參數(shù),做一些我們想做的事情

參考文章
http://www.itdecent.cn/p/7454a933dcaf
https://www.race604.com/annotation-processing/
https://tool.oschina.net/apidocs/apidoc?api=jdk-zh

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容