[轉]注解支持(Support Annotations)


  1. 注解支持(Support Annotations)
    Android support library從19.1版本開始引入了一個新的注解庫,它包含很多有用的元注解,你能用它們修飾你的代碼,幫助你發現bug。Support library自己本身也用到了這些注解,所以作為support library的用戶,Android Studio已經基于這些注解校驗了你的代碼并且標注其中潛在的問題。Support library 22.2版本又新增了13個新的注解以供使用。

  2. 使用注解庫
    注解默認是沒有包含的;他們被包裝成一個獨立的庫。(support library現在由一些更小的庫組成:v4-support, appcompat, gridlayout, mediarouter等等)(如果你正在使用appcompat庫,那么你已經可以使用這些注解了,因為appcomat它自己也依賴它。) 添加使用注解最簡單的方式就是打開Project Structure對話框。首先在左邊選中module,然后在右邊選中Dependencies標簽頁,點擊面板底部的+按鈕,選擇Library Dependency,假設你已經把Android Support Repository安裝到你的SDK中了,那么注解庫將會出現在列表中,你只需點擊選中它即可(這里是列表中的第一個):

    添加依賴

    點擊OK完成Project Structure的編輯。這會修改你的build.gradle文件,當然你也可以手動編輯它:

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

對于Android application和Android library這兩個類型的module(你應用了com.android.application或者com.android.library插件的)來說,你需要做的已經都做好了。如果你想只在Java module使用這些注解,那么你就明確的包含SDK倉庫了,因為support libraries不能從jcenter獲得(Android Gradle插件會自動的包含這些依賴,但是Java插件卻沒有。)

repositories {
  jcenter()
  maven { 
    url '<your-SDK-path>/extras/android/m2repository'           
  }
}
  1. 執行注解
    當你用Android Studio和IntelliJ的時候,如果給標注了這些注解的方法傳遞錯誤類型的參數,那么IDE就會實時標記出來。
    從Gradle插件1.3.0-beta1版本開始,并且安裝了Android M Preview平臺工具的情況下,通過命令行調用gradle的lint
    任務就可以執行這些檢查。如果你想把標記問題作為持續集成的一部分,那么這種方式是非常有用的。說明:這并不包含nullness注解。本文中所介紹的其他注解都可以通過lint執行檢查。

  2. 注解用法

  • Nullness Annotations
    @Nullable注解能被用來標注給定的參數或者返回值可以為null。類似的,@NonNull注解能被用來標注給定的參數或者返回值不能為null。
    如果一個本地變量的值為null(比如因為過早的代碼檢查它是否為null),而你又把它作為參數傳遞給了一個方法,并且該方法的參數又被@NonNull標注,那么IDE會提醒你,你有一個潛在的崩潰問題。

    v4 support library中的FragmentActivity的示例代碼:

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;  
@Nullable
@Override
public View onCreateView(String name, 
                @NonNull Context context, 
                @NonNull AttributeSet attrs) {
  (如果你執行Analyze > Infer Nullity,或者你在鍵入時把@NonNull替換成了@NotNull,那么IDE可能會提供附加的IntelliJ注解。參考底部的“IntelliJ Annotations”段落了解更多)

注意@NonNull和@Nullable并不是對立的:還有第三種可能:未指定。當你沒有指定@NonNull或者@Nullable的時候,工具就不能確定,所以這個API也就不起作用。
最初,我們在findViewById方法上標注@Nullable,從技術上說,這是正確的:findViewById可以返回null。但是如果你知道你在做什么的時候(如果你傳遞給他一個存在的id)他是不會返回null的。當我們使用@Nullable注解它的時候,就意味著源代碼編輯器中會有大量的代碼出現高亮警告。如果你已經意識到每次使用該方法都應該明確的進行null檢查,那么就只能用@Nullable標注返回值。有個經驗規則:看現有的“好的代碼”(比如審查產品代碼),看看這些API是怎么被使用的。如果該代碼為null檢查結果,你應該為方法注解@Nullable。

  • 資源類型注解
    Android的資源值通常都是使用整型傳遞。這意味著獲取一個drawable使用的參數,也能很容易的傳遞給一個獲取string的方法;因為他們都是int類型,編譯器很難區分。

    資源類型注解可以在這種情況下提供類型檢查。比如一個被@StringRes住進誒的int類型參數,如果傳遞一個不是R.string類型的引用將會被IDE標注:
    資源類型注解
    以ActionBar為例:
import android.support.annotation.StringRes;
public abstract void setTitle(@StringRes int resId);
有很多不同資源類型的注解:如下的每一個Android資源類型: @StringRes, @DrawableRes, @ColorRes, @InterpolatorRes,等等。一般情況下,如果有一個foo 類型的資源,那么它的相應的資源類型注解就是FooRes. 除此之外,還有一個名為@AnyRes特殊的資源類型注解。它被用來標注一個未知的特殊類型的資源,但是它必須是一個資源類型。比如在框架中,它被用Resources#getResourceName(@AnyRes int resId)

上,使用的時候,你可以這樣getResources().getResourceName(R.drawable.icon)
用,也可以getResources().getResourceName(R.string.app_name)
這樣用,但是卻不能這樣getResources().getResourceName(42)
用。
請注意,如果你的API支持多個資源類型,你可以使用多個注解來標注你的參數。

  • IntDef/StringDef: 類型定義注解
    整型除了可以作為資源的引用之外,也可以用作“枚舉”類型使用。
    @IntDef和”typedef”作用非常類似,你可以創建另外一個注解,然后用@IntDef指定一個你期望的整型常量值列表,最后你就可以用這個定義好的注解修飾你的API了。
    appcompat庫里的一個例子:
import android.support.annotation.IntDef;
public abstract class ActionBar {
    
    @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
    @Retention(RetentionPolicy.SOURCE)
    public @interface NavigationMode {}
    
    public static final int NAVIGATION_MODE_STANDARD = 0;
    public static final int NAVIGATION_MODE_LIST = 1;
    public static final int NAVIGATION_MODE_TABS = 2;
    
    @NavigationMode
    public abstract int getNavigationMode();
    public abstract void setNavigationMode(@NavigationMode int mode);
}
上面非注解的部分是現有的API。我們創建了一個新的注解(NavigationMode)并且用@IntDef標注它,通過@IntDef我們為返回值或者參數指定了可用的常量值。我們還添加了@Retention(RetentionPolicy.SOURCE)

告訴編譯器這個新定義的注解不需要被記錄在生成的.class文件中(譯者注:源代碼級別的,生成class文件的時候這個注解就被編譯器自動去掉了)。

使用這個注解后,如果你傳遞的參數或者返回值不在指定的常量值中的話,IDE將會標記出這種情況。
類型定義注解

你也可以指定一個整型是一個標記性質的類型;這樣客戶端代碼就通過|,&等操作符同時傳遞多個常量了:
@IntDef(flag=true, value={
        DISPLAY_USE_LOGO,
        DISPLAY_SHOW_HOME,
        DISPLAY_HOME_AS_UP,
        DISPLAY_SHOW_TITLE,
        DISPLAY_SHOW_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}
最后,還有一個字符串版本的注解,就是@StringDef,它和@IntDef的作用基本上是一樣,所不同的是它是針對字符串的。該注解一般不常用,但是有的時候非常有用,比如在限定向Activity#getSystemService方法傳遞的參數范圍的時候。

要了解關于類型注解的更多詳細信息,請參考https://developer.android.com/tools/debugging/annotations.html#enum-annotations

  • 線程注解: @UiThread, @WorkerThread, … (Support library 22.2及其之后版本支持.)
    如果你的方法只能在指定的線程類型中被調用,那么你就可以使用以下4個注解來標注它:
    @UiThread
    @MainThread
    @WorkerThread
    @BinderThread

    如果一個類中的所有方法都有相同的線程需求,那么你可以注解類本身。比如android.view.View,就被用@UiThread標注。
    關于線程注解使用的一個很好的例子就是AsyncTask:

@WorkerThread
protected abstract Result doInBackground(Params... params);
@MainThread
protected void onProgressUpdate(Progress... values) {}
如果你在重寫的doInBackground方法里嘗試調用onProgressUpdate方法或者View的任何方法,IDE工具就會馬上把它標記為一個錯誤:![線程注解](http://upload-images.jianshu.io/upload_images/1642441-1db1f71c2ba18464.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

@UiThread還是@MainThread?
在進程里只有一個主線程。這個就是@MainThread。同時這個線程也是一個@UiThread。比如activity的主要窗口就運行在這個線程上。然而它也有能力為應用創建其他線程。這很少見,一般具備這樣功能的都是系統進程。通常是把和生命周期有關的用@MainThread標注,和View層級結構相關的用@UiThread標注。但是由于@MainThread本質上是一個@UiThread,而大部分情況下@UiThread又是一個@MainThread,所以工具(lint ,Android Studio,等等)可以把他們互換,所以你能在一個可以調用@MainThread方法的地方也能調用@UiThread方法,反之亦然。

  • RGB顏色整型
    當你的API期望一個顏色資源的時候,可以用@ColorRes標注,但是當你有一個相反的使用場景時,這種用法就不可用了,因為你并不是期望一個顏色資源id,而是一個真實的RGB或者ARGB的顏色值。
    在這種情況下,你可以使用@ColorInt注解,表示你期望的是一個代表顏色的整數值:
public void setTextColor(@ColorInt int color){}

有了這個,當你傳遞一個顏色id而不是顏色值的時候,lint就會標記出這段不正確的代碼:
顏色值注解
  • 值約束: @Size, @IntRange, @FloatRange
    如果你的參數是一個float或者double類型,并且一定要在某個范圍內,
    你可以使用@FloatRange注解:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {}

如果有人使用該API的時候傳遞一個0-255的值,比如嘗試調用setAlpha(128),那么工具就會捕獲這一問題:


值約束注解

(你也可以指定是否包括起始值。)
同樣的,如果你的參數是一個int或者long類型,你可以使用@IntRange注解約束其值在一個特定的范圍內:

public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }

把這些注解應用到參數上是非常有用的,因為用戶很有可能會提供錯誤范圍的參數,比如上面的setAlpha例子,有的API是采用0-255的方式,而有的是采用0-1的float值的方式。
最后,對于數據、集合以及字符串,你可以用@Size注解參數來限定集合的大小(當參數是字符串的時候,可以限定字符串的長度)。

舉幾個例子
集合不能為空: @Size(min=1)
字符串最大只能有23個字符: @Size(max=23)
數組只能有2個元素: @Size(2)
數組的大小必須是2的倍數 (例如圖形API中獲取位置的x/y坐標數組:

@Size(multiple=2)
值約束注解
  • 權限注解: @RequiresPermission
    如果你的方法的調用需要調用者有特定的權限,你可以使用@RequiresPermission注解:
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;

如果你至少需要權限集合中的一個,你可以使用anyOf屬性:

@RequiresPermission(anyOf = { 
    Manifest.permission.ACCESS_COARSE_LOCATION, 
    Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String provider);

如果你同時需要多個權限,你可以用allOf屬性:

@RequiresPermission(allOf = { 
    Manifest.permission.READ_HISTORY_BOOKMARKS, 
    Manifest.permission.WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) {}

對于intents的權限,可以直接在定義的intent常量字符串字段上標注權限需求(他們通常都已經被@SdkConstant注解標注過了):

@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";

對于content providers的權限,你可能需要單獨的標注讀和寫的權限訪問,所以可以用@Read或者@Write標注每一個權限需求:

@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
權限注解
  • 方法重寫: @CallSuper
    如果你的API允許使用者重寫你的方法,但是呢,你又需要你自己的方法(父方法)在重寫的時候也被調用,這時候你可以使用@CallSuper標注:
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {}

用了這個后,當重寫的方法沒有調用父方法時,工具就會給予標記提示:


方法重寫

(Android Studio 1.3 Preview 1的lint檢查有個關于這個注解的bug,這個bug就是即使是對的重寫也會報錯,這個bug已經在Preview 2版本修改,可以通過canary channel更新到Preview 2版本。)

  • 返回值: @CheckResult
    如果你的方法返回一個值,你期望調用者用這個值做些事情,那么你可以使用@CheckResult注解標注這個方法。
    你并不需要微每個非空方法都進行標注。它主要的目的是幫助哪些容易被混淆,難以被理解的API的使用者。
    比如,可能很多開發者都對String.trim()一知半解,認為調用了這個方法,就可以讓字符串改變以去掉空白字符。如果這個方法被@CheckResult標注,工具就會對那些沒有使用trim()返回結果的調用者發出警告。
    Android中,Context#checkPermission這個方法已經被@CheckResult標注了:
@(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);

這是非常重要的,因為有些使用context.checkPermission的開發者認為他們已經執行了一個權限 —-但其實這個方法僅僅只做了檢查并且反饋一個是否成功的值而已。如果開發者使用了這個方法,但是又不用其返回值,那么這個開發者真正想調用的可能是這個Context#enforcePermission方法,而不是checkPermission。


返回值
  • @VisibleForTesting
    你可以把這個注解標注到類、方法或者字段上,以便你在測試的時候可以使用他們。

  • @Keep
    我們還在注解庫里添加了@Keep注解,但是Gradle插件還支持(盡管已經在進行中)。被這個注解標注的類和方法在混淆的時候將不會被混淆。
    在你自己的庫中使用注解
    如果你在你自己的庫中使用了這些注解,并且是通過Gradle構建生成aar包,那么在構建的時候Android Gradle插件會提取注解信息放在AAR文件中供引用你的庫的客戶端使用。在AAR文件中你可以看到一個名為annotations.zip的文件,這個文件記錄的就是注解信息,使用的是IntelliJ的擴展注解XML格式。這是必須的,因為.class文件不能包含足夠的要處理以上@IntDef注解的信息;注意我們只需記錄該常量的一個引用,而不是它的值。當且僅當你的工程依賴注解庫的時候,Android Gradle插件會把提取注解的任務作為構建的一部分執行它。(說明:只有源保留注解被放置在.aar文件中;class級別的會被放在classes.jar里。)

  • IntelliJ注解
    IntelliJ,Android Studio就是基于它開發的,IntelliJ有一套它自己的注解;IntDef分析其實重用的是MagicConstant分析的代碼,IntelliJ null分析其實用的是一組配置好的null注解。如果你執行Analyze > Infer Nullity…,它會試圖找出所有的null約束并添加他們。這個檢查有時會插入IntelliJ注解。你可以通過搜索,替換為Android注解庫的注解,或者你也可以直接用IntelliJ注解。在build.gradle里或者通過Project Structure對話框的Dependencies面板都可以添加如下依賴:

dependencies { 
    compile 'com.intellij:annotations:12.0'
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容