天天看點

Android Annotation注解學習筆記

今天講下注解吧,現在遇到的用注解的開源庫越來越多,雖然知道怎麼用,但是其原理,怎麼寫都還不清楚。學習了一些網站和資料,先共享下我的學習資料:

Java Annotation 及幾個常用開源項目注解原理簡析

安卓注解那些事兒

Android注解支援(Support Annotations)

Java基礎加強總結(一)——注解(Annotation)

注解的分類方式有很多:

  1. 标準的Annotation:override、deprecated、SuppressWarnings等(這些為Java自帶)。
  2. 元Annotation:這些注解是用來修飾注解的,例如Target(說明該注解是用來形容哪些程式元素的,例如Method、Field、Class等)、Retention(指明注解的生命周期,後面會詳述)、Documented(是否儲存到Javadoc中)、Inherited(能否被繼承)。
  3. 其他Annotation:包括Android已經提供的Annotation(support-annotation包中)和自定義的Annotation。

當然還有其他的分類方式,根據注解的Retention來區分,剛剛說到Retention是注解的生命周期,那分為Source(源碼時注解)、Class(編譯時注解)、Runtime(運作時注解)。@Retention(RetentionPolicy.SOURCE)這個注解的意思是讓注解隻在java源檔案中存在,編譯成.class檔案後注解就不存在了。@Retention(RetentionPolicy.CLASS)這個注解的意思是讓注解在java源檔案(.java檔案)中存在,編譯成.class檔案後注解也還存在,被注解類辨別的類被類加載器加載到記憶體中後MyAnnotation注解就不存在了。如果是Runtime的話,則加載到記憶體中,該注解還存在,下面是詳細解釋:

當在Java源程式上加了一個注解,這個Java源程式要由javac去編譯,javac把java源檔案編譯成.class檔案,在編譯成class時可能會把Java源程式上的一些注解給去掉,java編譯器(javac)在處理java源程式時,可能會認為這個注解沒有用了,于是就把這個注解去掉了,那麼此時在編譯好的class中就找不到注解了, 這是編譯器編譯java源程式時對注解進行處理的第一種可能情況,假設java編譯器在把java源程式編譯成class時,沒有把java源程式中的注解去掉,那麼此時在編譯好的class中就可以找到注解,當程式使用編譯好的class檔案時,需要用類加載器把class檔案加載到記憶體中,class檔案中的東西不是位元組碼,class檔案裡面的東西由類加載器加載到記憶體中去,類加載器在加載class檔案時,會對class檔案裡面的東西進行處理,如安全檢查,處理完以後得到的最終在記憶體中的二進制的東西才是位元組碼,類加載器在把class檔案加載到記憶體中時也有轉換,轉換時是否把class檔案中的注解保留下來,這也有說法,是以說一個注解的生命周期有三個階段:java源檔案是一個階段,class檔案是一個階段,記憶體中的位元組碼是一個階段,javac把java源檔案編譯成.class檔案時,有可能去掉裡面的注解,類加載器把.class檔案加載到記憶體時也有可能去掉裡面的注解,是以在自定義注解時就可以使用Retention注解指明自定義注解的生命周期,自定義注解的生命周期是在RetentionPolicy.SOURCE階段(java源檔案階段),還是在RetentionPolicy.CLASS階段(class檔案階段),或者是在RetentionPolicy.RUNTIME階段(記憶體中的位元組碼運作時階段),根據JDK提供的API可以知道預設是在RetentionPolicy.CLASS階段 (JDK的API寫到:the retention policy defaults to RetentionPolicy.CLASS.)

如果是Source和Class的話,即使引起Android Studio報錯,也不會影響運作,依舊可以運作。舉個簡單的例子,EventBus庫(最新的版本onEvent函數使用了@Subscribe注解),我們知道其原理是當對象注冊了EventBus後,EventBus會記錄該類onEvent方法,當檢測到有消息post出來後,會調用有subscribe注解的方法,試想一下,如果這歌subscribe是source或者class的話,那麼加載到記憶體的時候,就已經沒有了,那麼Eventbus找不到任何方法來處理事件,是以subscribe一定是運作時,看下代碼:

package org.greenrobot.eventbus;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * If true, delivers the most recent sticky event (posted with
     * {@link EventBus#postSticky(Object)}) to this subscriber (if event available).
     */
    boolean sticky() default false;

    /** Subscriber priority to influence the order of event delivery.
     * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before
     * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of
     * delivery among subscribers with different {@link ThreadMode}s! */
    int priority() default ;
}
           

果然沒錯,驗證了我們的猜想。

安卓中的注解分為八大類:

  • Nullness注解:@Nullable、@NoNull(為方法的形參加上這個,可以減少判空的代碼)
  • 資源類型注解:以Res結尾,例如@StringRes、@ColorRes、@IdRes等
  • 權限注解:@requestPermission,這個注解在PermissionDispatcher這個開源庫中用到
  • CallSuper注解:被@CallSuper修飾的方法,一定要調用super方法
  • 枚舉注解:@IntDef和@StringDef,具體用法可以看Toast下Duration
  • 線程注解:@MainThread、@WorkThread等,被這些注解修飾的方法隻能在該線程内調用
  • 變量限制注解:@Size、@IntRange、@FloatRange等
  • 結果檢查注解:@CheckResult,被該注解修飾的方法,需要對方法的傳回值進行處理。例如Context.checkPermission(@NonNull String permission, int pid, int uid)方法,防止别人誤解該方法就已經算是請求權限了,調用該方法一定要判斷傳回值,權限是否被賦予,如果不判斷,調用該方法無用。如果隻是簡單調該函數,并未判斷傳回值,則會提示是否使用另外一個函數。
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
           

怎麼寫一個注解可以參照上面共享文章中的最後一篇:Java基礎加強總結(一)——注解(Annotation)

講到現在都還沒舉一個具體的例子,那就讓我們來看看常見的ColorRes:

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface ColorRes {
}
           

解釋下上述代碼:該注解将會儲存到Javadoc中;該注解是編譯時注解,在class檔案中還會存在,但是在記憶體中就沒了;該注解用來修飾方法、參數、屬性、局部變量。

還有一些比較好用的注解,例如@Keep,該注解表示被該注解修飾的元素将不會被混淆。

下面講下編譯時解析:由apt(Annotation Processing Tool)自動解析

  1. 自定義類繼承自AbstractProcessor
  2. 重寫該類的Process函數

    apt在編譯時自動查找所有繼承自AbstractProcessor的類,調用process函數去處理。