Android 編譯時註解 —— 語法詳解

爲何要寫這一系列的博客呢?java

由於在 Android 開發的過程當中, 泛型,反射,註解這些知識進場會用到,幾乎全部的框架至少都會用到上面的一兩種知識,如 Gson 就用到泛型,反射,註解,Retrofit 也用到泛型,反射,註解 。學好這些知識對咱們進階很是重要,尤爲是閱讀開源框架源碼或者本身開發開源框架。android

java Type 詳解程序員

java 反射機制詳解面試

註解使用入門(一)bash

Android 自定義編譯時註解1 - 簡單的例子微信

Android 編譯時註解 —— 語法詳解框架

帶你讀懂 ButterKnife 的源碼ide

通過前面的介紹,相信你們對註解有了必定的瞭解了。ui

根據註解使用方法和用途,咱們能夠將Annotation分爲三類:spa

  • JDK內置系統註解(如 @SuppressWarnings("deprecation"),@override 等)
  • 元註解 如(@Documented ,@Retention() ,@Target(),@Documented )
  • 自定義註解 (本身實現的的註解)

元註解

元註解 解析說明

  • @Documented 是否會保存到 Javadoc 文檔中

  • @Retention 保留時間,可選值, 默認爲 CLASS

    SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時)

  • @Target 能夠用來修飾哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標註則表示可修飾全部

ANONOTATION_TYPE(註解類型聲明),
PACKAGE(包)
TYPE (類,包括enum及接口,註解類型)
METHOD (方法)
CONSTRUCTOR (構造方法)
FIFLD (成員變量)
PARAMATER (參數)
LOCAL_VARIABLE (局部 變量)
複製代碼
  • @Inherited 是否能夠被繼承,默認爲 false

須要注意的是註解是不能夠繼承的,@Inherited 的意思是 加入咱們把註解應用到 A 類中,B 類繼承 A ,那麼 B 能夠掃描到 A 的註解。

註解的繼承」(依賴倒置?)

這裏講的繼承並非經過@Inherited修飾的註解。

這個「繼承」是一個註解的使用技巧,使用上的感受相似於依賴倒置,來自於ButterKnife源碼。

先看代碼。

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
      /** View IDs to which the method will be bound. */
      int[] value() default { View.NO_ID };
}
複製代碼

這是ButterKnife的OnClick 註解。特殊的地方在於@OnClick修飾了註解@ListenerClass,而且設置了一些只屬於@OnClick的屬性。

那這樣的做用是什麼呢?

凡是修飾了@OnClick的地方,也就自動修飾了@ListenerClass。相似於@OnClick是@ListenerClass的子類。而ButterKnife有不少的監聽註解@OnItemClick、@OnLongClick等等。

這樣在作代碼生成時,不須要再單獨考慮每個監聽註解,只須要處理@ListenerClass就OK。如 @interface OnItemClick 等。

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.widget.AdapterView<?>",
    setter = "setOnItemClickListener",
    type = "android.widget.AdapterView.OnItemClickListener",
    method = @ListenerMethod(
        name = "onItemClick",
        parameters = {
            "android.widget.AdapterView<?>",
            "android.view.View",
            "int",
            "long"
        }
    )
)
public @interface OnItemClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}
複製代碼

自定義註解

一個簡單的自定義註解例子

@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target({ElementType.FIELD, ElementType.TYPE}) 
public @interface Seriable {
     
 
}
複製代碼

指定默認值

@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target({ElementType.FIELD, ElementType.TYPE}) 
public @interface Seriable {
     int id();
     String name() default "test";
}

//使用
@Seriable(id = 1) //name有默認值能夠不寫
class Test{
}
複製代碼

關於怎樣自定義一個註解,能夠參看這一篇博客,Android 自定義編譯時註解1 - 簡單的例子


處理器類Processor編寫

自定義註解後,須要編寫Processor類處理註解。Processor 繼承自 AbstractProcessor 的類。咱們主要須要處理如下連個方法。

  • public Set getSupportedAnnotationTypes()

  • public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);

getSupportedAnnotationTypes 方法

重寫 getSupportedAnnotationTypes 方法:告知Processor哪些註解須要處理。返回一個Set集合,集合內容爲 自定義註解的包名+類名

建議項目中這樣編寫:

@Override
public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    //須要全類名
    types.add(Seriable.class.getCanonicalName()); 
    types.add(Println.class.getCanonicalName());
    return types;
}
複製代碼

另外若是註解數量不多的話,能夠經過另外一種方式實現:

//在只有一到兩個註解須要處理時,能夠這樣編寫:
@SupportedAnnotationTypes({"com.example.Seriable"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class JsonProcessor extends AbstractProcessor {
}
複製代碼

process 方法

process 方法,這個方法是全部方法中最關鍵的一個方法,他用來處理註解的相關信息,好比提取註解的信息,存進 map 集合或者生成代碼等。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    return false;
}
複製代碼
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    //   第一步,根據咱們自定義的註解拿到 elememts set 集合
    Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
    TypeElement typeElement;
    VariableElement variableElement;
 
    //  第二步: 根據 element 的類型作相應的處理,並存進 map 集合
    for (Element element : elememts) {
        ElementKind kind = element.getKind();
        // 判斷該元素是否爲類
        if (kind == ElementKind.CLASS) {
            typeElement = (TypeElement) element;
            // 判斷該元素是否爲成員變量
        } else if (kind == ElementKind.FIELD) {
            variableElement = (VariableElement) element;
           
        
        }
    }

  

    return true;
}
複製代碼

Element

元素,雖有經過註解取得的元素都以 Element 等待處理,它的具體類型與咱們經過 @Target 來標記的具備必定的聯繫。

官方的解釋是這樣的:

Represents a program element such as a package, class, or method.Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine)

表示程序元素如包、類或者方法。每一個元素表明一個靜態語言級構造(例如,而不是運行時構建的虛擬機)

例如:

// 第一步,根據咱們自定義的註解拿到 elememts set 集合
    Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
    TypeElement typeElement;
    VariableElement variableElement;
 
    //  第二步: 根據 element 的類型作相應的處理,並存進 map 集合
    for (Element element : elememts) {
        ElementKind kind = element.getKind();
        // 判斷該元素是否爲類
        if (kind == ElementKind.CLASS) {
            typeElement = (TypeElement) element;
            // 判斷該元素是否爲成員變量
        } else if (kind == ElementKind.FIELD) {
            variableElement = (VariableElement) element;
           
        
        }
    }
複製代碼

Element 的子類

Element 的子類有:

  • ExecutableElement

表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註釋類型元素。

對應@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)

  • PackageElement;

表示一個包程序元素。提供對有關包極其成員的信息訪問。

對應@Target(ElementType.PACKAGE)

  • TypeElement;

表示一個類或接口程序元素。提供對有關類型極其成員的信息訪問。

對應@Target(ElementType.TYPE)

注意:枚舉類型是一種類,而註解類型是一種接口。

  • TypeParameterElement;

表示通常類、接口、方法或構造方法元素的類型參數。

對應@Target(ElementType.PARAMETER)

  • VariableElement;

表示一個字段、enum常量、方法或構造方法參數、局部變量或異常參數。

對應 @Target(ElementType.LOCAL_VARIABLE)

判斷 Element 具體屬於哪種子類

咱們能夠經過 element.getKind(); 來獲得 Element 究竟是哪種類別,該方法返回 ElementKind 類型,咱們在根據 ElementKind 便可得出 Element 究竟是哪一種類別。

ElementKind kind = element.getKind();
// 判斷該元素是否爲類
if (kind == ElementKind.CLASS) {
    typeElement = (TypeElement) element;
} else if (kind == ElementKind.FIELD) {
   variableElement = (VariableElement) element;
}

複製代碼

看到這裏,想起前面的一篇博客 java Type 詳解 ,咱們須要注意 Type 與 Element 的區別。

Type 是用來處理泛型的,Element 是用來處理註解的。


題外話

註解語法暫時介紹到這裏,之後想到新的,會逐步更新到這篇博客,下一篇,將分析 ButterKnife 源碼。

相關博客推薦

java Type 詳解

java 反射機制詳解

註解使用入門(一)

Android 自定義編譯時註解1 - 簡單的例子

Android 編譯時註解 —— 語法詳解

帶你讀懂 ButterKnife 的源碼

掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。

相關文章
相關標籤/搜索