自定義Android註解Part1:註解變量

clipboard.png

對於Android註解,或多或少都有一點接觸,但相信大多數人都是在使用其它依賴庫的時候接觸的。由於有些庫若是你想使用它就必須使用它所提供的註解。例如:ButterKnife、Dagger二、Room等等。java

至於爲什麼使用註解?使用過的應該都知道,最明顯的就是方便、簡潔。經過使用註解能夠在項目編譯階段,幫助咱們自動生成一些重複的代碼,減輕咱們的負擔。典型的ButterKnife本質就是使用Android註解,經過註解來減小咱們對view.findViewById的編寫,提升咱們的開發效率。上一個系列(AAC)的Room也是同樣,咱們能夠簡單的回顧一下:android

@Entity(tableName = "contacts")
data class ContactsModel(
        @PrimaryKey
        @ColumnInfo(name = "contacts_id")
        val id: Int,
        @ColumnInfo(name = "name")
        val name: String,
        @ColumnInfo(name = "phone")
        val phone: String
)

經過使用註解來定義一個實體表,也就10行左右的代碼。若是要咱們所有本身寫那絕對要兩三百行代碼了,並且其中還可能出錯,又要改bug等等。效率就嚴重下降。對於依賴庫若是都這麼麻煩也就不會有人用了。git

那麼如何判斷一個依賴庫是否須要使用註解呢?其實很簡單,只要記住如下兩點便可:github

  1. 須要生成的代碼不能與項目邏輯有關
  2. Android註解只能生成代碼,並不能修改代碼
這裏透露一下,Android註解的本質是使用Java的反射機制,後續會詳細說明

項目架構

相信ButterKnife應該有接觸過吧,沒有的也不要緊,如今正是時候。下面咱們會本身實現BindView與OnClick註解,實現ButterKnife中的對應註解功能。那麼我先來看下總體的項目架構segmentfault

clipboard.png

經過項目圖,咱們能夠清晰的看到,主要分爲三個部分api

  1. butterknife-annotations:註解庫,包含BindView與OnClick等自定義的註解
  2. butterknife-bind:綁定庫,自定義的註解與聲明的類綁定
  3. butterknife-compiler: 解析編譯生成庫,解析聲明類中的註解,在編譯時自動生成相應的代碼。

爲了幫助你們可以更輕鬆的理解Android註解,今天主要分析的就是butterknife-annotations這個註解庫。帶你們一塊兒來聲明註解變量。數組

BindView

爲了要實現開源庫butterknife相似的綁定id效果,這裏咱們先定義一個BindView註解,具體以下:架構

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
    @IdRes int[] value();
}

嗯,仍是很簡單的對吧。也就5行代碼解決BindView註解的定義。app

那麼再來詳細剖析這5行代碼。函數

Retention

首先是第一行代碼的Retention,看它的使用方式就能知道,它也是一個聲明瞭的註解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

經過源碼咱們能夠看出該註解只接收一個參數,該參數爲RetentionPolicy類型。那麼咱們在進一步深刻RetentionPolicy:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
 
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
 
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

在這裏咱們發現它實際上是一個枚舉,在枚舉中支持三個常量,分別爲SOURCE、CLASS與RUNTIME。它們的區別主要是做用的週期範圍,下面我再對這三個的做用進行翻譯一遍:

  1. SOURCE: 使用該標明的註解將在編譯階段就被拋棄掉。
  2. CLASS:使用該標明的註解將在編譯階段記錄到生成的class文件中,但在運行階段時又會被VM拋棄。默認是該模式。
  3. RUNTIME:使用該標明的註解將在編譯階段被保存在生成的class文件中,同時在運行階段時會保存到VM中。因此它該註解將一直存在,天然可以經過java的反射機制進行讀取。

因此它們的存在的生命時長爲SOURCE < CLASS < RUNTIME。知道了它的做用範圍以後,咱們在自定義註解時就要儘可能較小注解的做用範圍,提升項目的編譯與運行速度。

由於咱們的BindView註解只是爲了進行Viwe的綁定,因此在編譯以後就無需存在,因此這裏就使用了CLASS來進行標明。

Target

下面咱們在來看第二行代碼,這裏使用到了另外一個註解Target,咱們仍是來看下它的源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

能夠看到註解的源碼都很是簡單,這裏接收了一個ElementType數組參數,ElementType不難猜出它的類型也是一個枚舉:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
 
    /** Field declaration (includes enum constants) */
    FIELD,
 
    /** Method declaration */
    METHOD,
 
    /** Formal parameter declaration */
    PARAMETER,
 
    /** Constructor declaration */
    CONSTRUCTOR,
 
    /** Local variable declaration */
    LOCAL_VARIABLE,
 
    /** Annotation type declaration */
    ANNOTATION_TYPE,
 
    /** Package declaration */
    PACKAGE,
 
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

ElementType中雖然有10常量,但咱們實際真正經常使用的也就是前面8種。它們表明自定義的註解可以做用的對象。分別爲:

  1. TYPE: 做用於類、接口或者枚舉
  2. FIELD:做用於類中聲明的字段或者枚舉中的常量
  3. METHOD:做用於方法的聲明語句中
  4. PARAMETER:做用於參數聲明語句中
  5. CONSTRUCTOR:做用於構造函數的聲明語句中
  6. LOCAL_VARIABLE:做用於局部變量的聲明語句中
  7. ANNOTATION_TYPE:做用於註解的聲明語句中
  8. PACKAGE:做用於包的聲明語句中
  9. TYPE_PARAMETER:java 1.8以後,做用於類型聲明的語句中
  10. TYPE_USE:java 1.8以後,做用於使用類型的任意語句中

結合咱們的BindView的做用是對View進行id綁定,天然是做用與聲明的字段上。因此在BindView中使用了FIELD。

再來看第四行代碼

@IdRes int[] value()

有了上面的註解接觸,不難理解這是標明BindView將接收一個int類型的數組參數。對於開源庫butterknife中的BindView是接收須要綁定的View的id,這裏咱們作一個改版,再接收一個String的id,用來爲綁定的View設置默認值。這樣咱們自定義了的BindView註釋就完成了。

OnClick

下面咱們再自定義一個OnClick點擊的註解,通過上面的分析,能夠在腦海中想一想Retention與Target分別什麼值?

想好了以後咱們在來過一遍

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface OnClick {
    @IdRes int value();
}

Retention的做用範圍與BindView同樣首頁SOURCE,在編譯以後就無需存在;Target的做用對象與BindView不一樣,既然是點擊事件的點擊操做,天然是做用在操做邏輯的方法上,因此這裏使用METHOD。

keep

文章開頭有說起到本質是經過註解來自動生成代碼,爲咱們建立所需的類,那麼在實際開發中一旦咱們的項目混淆了,這將會致使自動建立的類失效,從而致使咱們自定義的註解失效。因此爲了防止其失效,咱們在這裏再定義一個註解keep:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Keep {
}

Retention的做用範圍是在class文件中還要可以被其它class調用,因此這裏使用CLASS;Target做用對象是自動生成的類,因此使用TYPE。至於參數則沒必要要,它只是爲了標明類,防止其被混淆。

總結

butterknife-annotations庫中的自定義註解就完成了。經過上面的分析,咱們注意點主要歸結於如下三點:

  1. Retention: 明確註解做用的生命時長,儘早的消除
  2. Target: 明確註解做用的對象
  3. Keep: 防止後續自動生成的類被混淆

註解變量的定義就到這結束了,同時文章中的代碼均可以在Github中獲取到。使用時請將分支切換到feat_annotation_processing

若是感受不錯的話,能夠點贊收藏哦,這是對我最大的支持,謝謝!

相關文章

自定義Android註解Part2:代碼自動生成

自定義Android註解Part3:綁定

關注

私人博客

clipboard.png

相關文章
相關標籤/搜索