Android註解-看這篇文章就夠了

前言

註解,也被稱爲元數據,爲我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些數據。(Java編程思想)
很多文章都是講述java註解的,而且很多例子雖然有和Android互通的部分,但是Android開發中也擴展了很多單純Java中沒有的註解應用。所以這裏主要介紹Android開發中的註解,當然包括Java註解。
目前很多框架開發或者Android開發中都用到了註解,SDK開發中也有很多可以對接口添加限制以規範用戶使用的規則,這些都是值得我們去學習的。

Java註解

Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制。 Java 語言中的類、方法、變量、參數和包等都可以被標註。和 Javadoc 不同,Java 標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java 虛擬機可以保留標註內容,在運行時可以獲取到標註內容 。 當然它也支持自定義 Java 標註。
Java內置了三種標準註解,其定義在java.lang中。

  • @Override,表示當前的方法定義將覆蓋超類中的方法。
  • @Deprecated,被此註解標記的元素表示被廢棄,如果程序員使用了註解爲它的元素,那麼編譯器會發出警告。
  • @SuppressWarnings,關閉不當的編譯器警告信息。
    這幾種註解我們平時開發中肯定經常用到,但是你可能很少看到註解原來的樣子。如下爲註解的類,可以看到,除了@符號的使用以外,它基本與Java固有語法一致。只是註解上又增加了其他註解。
 @Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

那註解的註解又是啥呢,這裏就要引出接下來要說的,元註解,專職負責註解其他的註解。
四種元註解

元註解 說明 取值
@Target 表示該註解可以用在什麼地方 ElementType.ANNOTATION_TYPE 可以應用於註釋類型。
ElementType.CONSTRUCTOR 可以應用於構造函數。
ElementType.FIELD 可以應用於字段或屬性。ElementType.LOCAL_VARIABLE 可以應用於局部變量。
ElementType.METHOD 可以應用於方法級註釋。
ElementType.PACKAGE 可以應用於包聲明。
ElementType.PARAMETER 可以應用於方法的參數。ElementType.TYPE 可以應用於類的任何元素。
@Retention 表示需要在什麼級別保存該註解信息 1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)
@Documented 表示將此註解包含在Javadoc中
@Inherited 表示允許子類繼承父類中的註解

經過對元註解的表格進行理解,可以回頭看我們平常使用的標準註解Override 、Deprecated 和SuppressWarnings 。可以看到他們分別都由元註解進行了註釋,並且我們能夠明白,爲啥@Override只能用在方法上(@Target),而不能用在類、構造函數、變量上,而@Deprecated就可以用在構造函數、變量、方法、包、參數等,因爲其代表的意義就可以表示它能夠標註的所有類型都是廢棄的元素。

另外,我們需要對Java程序或者Android程序的三個階段進行解釋。我們在as或者idea等IDE開發時的Java源碼時期,即SOURCE實際。然後經過gradle或其他構件工具編譯,變成了.Class文件,在Android apk中的dex文件中都是.class文件,此時即Retention的Class時期。之後便是程序運行階段,變成Jvm運行的RUNTIME時期,此時應用是執行狀態,如果定義註解爲RUNTIME,則此時的註解是保留的。
java的三個標準註解的@Retention,有兩個是RetentionPolicy.SOURCE,一個是RetentionPolicy.RUNTIME。從上述表格可以瞭解,source即源文件保留,即是.java文件中保留。Runtime即運行時有效,即在Android應用在運行時還會存在,當然如果是java程序運行時也保留。可以理解爲@Override 註釋的方法,只在代碼開發時有用即編碼時可以覆蓋父類方法,編譯後和運行中這個註解代表的意義就沒有了。
大多數時候,程序員主要是用元註解定義自己的註解,並編寫自己的處理器來處理它們,文章最後會簡單的寫個自定義註解並應用以加深理解。
擴展
上邊是經典的Java註解介紹,由於我們的Java開發都是基於JDK的,所以上述源碼定義都在JDK的源碼中。而由於新的JDK對舊版進行了擴充。
如@Repeatable 即是@since 1.8。 從JDK1.8引入的,可能使用比較少見,筆者也沒見過這個的應用目前。但是提這個的目的即任何語法都是供加速開發或者其他目的添加的,只不過我們自定義註解是爲了我們自己的邏輯、或者做框架的是爲了使用框架的人方便, JDK的開發人員是爲了所有Java開發和他們自己方便。所以雖然我們不是定義JDK語言的,但是我們任何人開發都可以使用註解這種方式,爲我們的代碼和框架服務。註解是不斷變化並發展的,當然我們Java或Android開發也是。
package java.lang.annotation;

/**
 * The annotation type {@code java.lang.annotation.Repeatable} is
 * used to indicate that the annotation type whose declaration it
 * (meta-)annotates is <em>repeatable</em>. The value of
 * {@code @Repeatable} indicates the <em>containing annotation
 * type</em> for the repeatable annotation type.
 *
 * @since 1.8
 * @jls 9.6 Annotation Types
 * @jls 9.7 Annotations
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

Android註解

本來在Android系統源碼中/frameworks/base/core/java/android/annotation中是有很多的註解的,但是呢,他們都是系統源碼用的註解,不是給我們這種開發人員用的,因爲所有的註解的註釋上都有@hide註釋。

 * {@hide}
 */
@Documented
@Retention(SOURCE)
@Target({METHOD, PARAMETER, FIELD})
public @interface AnyRes {
}

在這裏插入圖片描述然後呢,Android系統給我們提供的SDK中,只有兩個原生註解。位於android.annotation包中
@TargetApi 使高版本API的代碼在低版本SDK不報錯。
@SuppressLint 使用此標註讓Lint忽略指定的警告。
舉例:
@SuppressLint(“NewApi”)屏蔽一切新api中才能使用的方法報的android lint錯誤。 @SuppressLint(“HandlerLeak”) 在主線程用Handler處理消息出現時會有警告,提示你,這塊有內存泄露的危險,handler最好聲明爲static的

在這裏插入圖片描述然後系統以另外的形式即Android Support方式提供給我們其他的註解。這些註解不是默認加載的,它們被包裝爲一個單獨的庫。Support Library現在是由一些更小的庫組成的,包括:v4-support、appcompat、gridlayout、mediarouter等等。當然這裏的註解是沒有@hide註釋的,只是需要開發者去加載對應的dependence。然後Android Studio實在基於這些註解來檢查代碼中的潛在問題了。如果升級到androidx,也有對應的dependence。

dependencies {
    compile 'com.android.support:support-annotations:XXX'
}

在這裏插入圖片描述

  • Nullness Annotations 參數
    @NonNull 該註釋表明使用的參數或者返回值不能爲null
    如果本地變量已知爲空,並且我們申明瞭@NonNull, IDE會使用Warn警告提醒存在潛在的崩潰。
    @Nullable 註釋表明使用的的參數或者返回值可以爲null
  • Resource Annotations,資源引用限制類:用於限制參數必須爲對應的資源類型。
    AnimatorRes :animator資源類型
    AnimRes:anim資源類型
    AnyRes:任意資源類型
    ArrayRes:array資源類型
    AttrRes:attr資源類型
    BoolRes:boolean資源類型
    ColorRes:color資源類型
    DimenRes:dimen資源類型。
    DrawableRes:drawable資源類型。
    FractionRes:fraction資源類型
    IdRes:id資源類型
    IntegerRes:integer資源類型
    InterpolatorRes:interpolator資源類型
    LayoutRes:layout資源類型
    MenuRes:menu資源類型
    PluralsRes:plurals資源類型
    RawRes:raw資源類型
    StringRes:string資源類型
    StyleableRes:styleable資源類型
    StyleRes:style資源類型
    TransitionRes:transition資源類型
    XmlRes:xml資源類型

作用舉例:資源裏的R.layout.和R.string.,二者都是int值,在源碼階段,如果在setContentView(值)的值設爲R.string.* 理論上是不是也能編譯通過?但是實際上不能,如果你這樣做了,就會顯示紅色警告,爲什麼呢。

在這裏插入圖片描述
原因就在源碼裏,我們可以點進去看源碼。在setContentView的參數裏有**@LayoutRes**的註解,告訴用戶這個int值必須是代表layout的。其他的資源類註解同理。

@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}
  • Thread annotations 線程執行限制類:用於限制方法或者類必須在指定的線程執行。如果方法代碼運行線程和標註的線程不一致,則會導致警告。
    @AnyThread
    @BinderThread
    @MainThread
    @UiThread
    @WorkerThread

  • Value Constraint Annotations 類型範圍限制類:用於限制標註值的值範圍

    @FloatRang
    @IntRange
    如下爲@FloatRange的註釋上的示例, 其代表返回值爲從0.0-1.0直接的float值。

	/**
 * Denotes that the annotated element should be a float or double in the given range
 * <p>
 * Example:
 * {@code
 * 	@FloatRange(from=0.0,to=1.0)
 *  public float getAlpha() {
 *      ...
 *  }
  • 類型定義類:用於限制定義的註解的取值集合

    @IntDef int型除了被作爲資源引用傳遞之外,還經常被作爲一種“枚舉”類型來使用。在創建一個註解的同時列出所有可用的有效值,然後再使用這個註解。也就是說@IntDef是用來修飾註解的註解(元註解),用它所修飾的註解在使用時就限定了被修飾對象的可取值範圍。
    @StringDef 類似IntDef,列舉出所有String的取值範圍。

    如下爲Intdef的源碼,可以看出,其@Target爲ANNOTATION_TYPE,和前邊java的元註解一致,所以這兩個也是元註解。

@Retention(CLASS)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    long[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;
}

這裏繼續舉例說明,自定義了一個Weather註解,而且定義了取值範圍,即IntDef中包含的選型。

 public static final int SUNNY = 1;
    public static final int CLOUDY = 2;
    public static final int RAIN = 3;
    public static final int SNOW = 4;

    @Target(ElementType.PARAMETER)
    @IntDef({
        SUNNY,
        CLOUDY,
        RAIN,
        SNOW
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Weather {
    }
    
    public void setWeather(@Weather int weather){
        
    }

使用時,如果傳入IntDef中定義的值,則顯示正常,否則編譯器會出發紅色警告。雖然,2值是包含在IntDef取值範圍的int值,但是仍不允許,必須寫對應常量。
在這裏插入圖片描述

  • 其他的功能性註解:
  • @RequiresPermission 權限註解,校驗方法調用者的權限。 anyOf 屬性用於檢查滿足一種一個, allOf屬性用於檢查滿足所有 attribute.
    筆者曾經因爲架構調整,會在新項目複製之前的代碼到新工程,但是隻複製了代碼,並沒檢查相應的權限,故版本發出後測試出了問題才檢查到這裏沒有申請權限。如果代碼規範,調用的方法上添加了權限檢查,則會在源碼時期就會發現問題。
    如果我在剛纔的例子上添加權限註解。你會發現,剛纔的正確的值也有了紅色警告,鼠標懸停可以看到,它需要添加權限。
 @RequiresPermission( Manifest.permission.READ_EXTERNAL_STORAGE)
public void setWeather(@Weather int weather) {

}

在這裏插入圖片描述

  • 其他註解,建議有興趣的同學查詢相關資料進行學習。
    @CallSuper 校驗 覆寫的方法 需要調用父類的實現方法。
    @CheckResult
    @ColorInt
    @Dimension
    @Keep
    @Px
    @RequiresApi
    @RestrictTo
    @Size
    @VisibleForTesting

使用註解的框架

  • JUnit:

    是一個Java語言的單元測試框架,可以大大縮短你的測試時間和準確度。
    @Test:進行測試的方法 。

    @BeforeClass : 該方法表示啓動測試類對象測試之前啓動的方法, 所以該方法必須是static 修飾的(可以通過類名直接訪問).一般用來打開配置文件,初始化資源等

    @AfterClass :該方法表示測試類對象測試完成之後啓動的方法, 所以該方法必須是static 修飾的(可以通過類名直接訪問).一般用來關閉數據庫,結束資源等

    @Before :該方法表示調用每個測試方法前都會被調用一次

    @After :該方法表示調用每個測試方法後都會被調用一次

    @Ignore :已經被忽略的測試方法 ,我們測試的話,會自動過濾掉
    ……

  • ButterKnife :

    Android 開發中 IOC 框架,它減少了大量重複的代碼。專注於Android系統的View注入框架,以前總是要寫很多findViewById來找到View對象,有了ButterKnife可以很輕鬆的省去這些步驟。並且使用ButterKnife對性能基本沒有損失,因爲ButterKnife用到的註解並不是在運行時反射的,而是在編譯的時候生成新的class。
    ButterKnife項目地址:https://github.com/JakeWharton/butterknife有需要研究的同學可以通過閱讀源碼深入學習。
    涉及註解有:
    @BindView
    @OnClick
    @BindString等。

  • Dagger2

    著名的依賴注入框架,依賴注入,顧名思義,就是說當代碼執行過程中需要某個服務對象的時候,不是通過當前代碼自己去構造或者去查找獲取服務對象,而是通過外部將這個服務對象傳給當前代碼。
    其自定義的註解有:
    @Inject
    @Moudle
    @Provider
    @Component
    @Singleton
    @Scope
    @Qualifier
    @Named
    對相關知識需要進一步學習的可以參考項目地址: https://github.com/google/dagger

  • Retrofit
    Retrofit 是一個 RESTful 的 HTTP 網絡請求框架的封裝,網絡請求的工作本質上是 OkHttp 完成,而 Retrofit 僅負責 網絡請求接口的封裝。Retrofit通過註解的方式,進行網絡請求描述。
    其常用註解有:
    @GET
    @POST
    @PUT
    @DELETE
    @PATCH
    @HEAD
    @OPTIONS
    @HTTP
    需要繼續深入瞭解的同學可以參考項目地址:https://github.com/square/retrofit

  • EventBus

    EventBus是一種用於Android的事件發佈-訂閱總線,由GreenRobot開發,它簡化了應用程序內各個組件之間進行通信的複雜度,尤其是碎片之間進行通信的問題,可以避免由於使用廣播通信而帶來的諸多不便。
    其主要的註解爲
    @Subscribe
    項目地址爲:https://github.com/greenrobot/EventBus

  • Arouter

    ARouter 是阿里開源的組件化框架,或者叫做路由框架。可以用來映射頁面關係,實現跳轉相關的功能。在 Android 中,常被用來進行組件化通訊。
    常用註解有:
    @Route
    @Interceptor
    @Autowired等。
    項目地址爲:https://github.com/alibaba/ARouter

自定義註解

爲了更好的瞭解註解的作用和框架如何使用註解打到自己的目的,方便用戶提高編碼效率。這裏模仿ButterKnife的功能實現一個註解。
首先自定義註解InjectDIYLayout , 這裏爲了簡單示例用了運行時註解,並採用反射方法調用執行。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectDIYLayout {
    int value();
}

然後寫工具類,在BaseActivity中的onCreate中初始化。InjectDIYUtils .inject(this);

public class InjectDIYUtils {
    private static final String TAG = "InjectDIYUtils";

    public static void inject(Object context){
        injectDIYLayout (context);
        }
}

之後就是註解的解釋了,就是injectLayout的方法體。這裏通過拿到參數activity的Context,並獲取Activity的類,之後通過類getAnnotation得到此類註釋的註解,經打印可以判斷是否此註解。然後通過反射獲取到setContentView,並用反射方法的invoke調用,傳入註解中的layout值。

private static void injectDIYLayout (Object context) {
        Class<?> aClass = context.getClass();
        InjectDIYLayout annotation = aClass.getAnnotation(InjectDIYLayout.class);
        Log.e(TAG, "injectLayout: annotation.value()="+annotation.value());
        try {
            Method contentView = aClass.getMethod("setContentView", int.class);

            try {

                Object invoke = contentView.invoke(context, annotation.value());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

使用時的代碼

@InjectDIYLayout(R.layout.activity_main)
public class MainActivity extends BaseActivity {

然後在Activity的onCreate方法中註釋setContentView。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView();

編譯看看,是不是能夠正常顯示。有興趣的同學可以自己創建工程測試下。

註解好處

首先,由於註解分了三個階段,有源碼階段、編譯階段、運行階段,不同階段的註解有不同作用。

  • 源碼期的註解,編譯檢查,Annotation
    具有"讓編譯器進行編譯檢查的作用",即上文說的如果未按要求編碼,則會顯示紅色警告。而且還可以利用APT技術進行自動化編碼。
  • 在反射中使用 Annotation,大家應該知道,反射必須在軟件運行後才能反射。作用麼,如上節示例,@InjectDIYLayout雖然優勢不明顯,但是如果例子是各種控件的,就如同butterKnife了,本來可能需要很多配置文件,需要很多邏輯才能實現的內容,就可以使用一個或者多個註解來替代,這樣就使得編程更加簡潔,代碼更加清晰。
  • 高級工程師開發肯定要能學習別人的框架源碼,如果懂了註解,上述使用了註解的框架基本就能讀懂了。
  • 最後,當你學會了註解,又熟悉了常用的框架,然後工作中你根據自己的場景寫出了自己的註解框架,用於開發效率提高。這難道不是一種很屌的事?接受別人讚賞的目光。

還等什麼呢,趕緊拿起電話訂購吧! 哦,不,是收藏這篇文章吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章