Android注解支持(Support Annotations)


注解支持(Support Annotations)

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

使用注解庫

注解默認是沒有包含的;他們被包裝成一個獨立的庫。(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' } } 

執行注解

當你用Android Studio和IntelliJ的時候,如果給標注了這些注解的方法傳遞錯誤類型的參數,那么IDE就會實時標記出來。

從Gradle插件1.3.0-beta1版本開始,並且安裝了Android M Preview平台工具的情況下,通過命令行調用gradle的lint任務就可以執行這些檢查。如果你想把標記問題作為持續集成的一部分,那么這種方式是非常有用的。說明:這並不包含nullness注解。本文中所介紹的其他注解都可以通過lint執行檢查。

Nullness Annotations

@Nullable注解能被用來標注給定的參數或者返回值可以為null。
類似的,@NonNull注解能被用來標注給定的參數或者返回值不能為null。

如果一個本地變量的值為null(比如因為過早的代碼檢查它是否為null),而你又把它作為參數傳遞給了一個方法,並且該方法的參數又被@NonNull標注,那么IDE會提醒你,你有一個潛在的崩潰問題。

v4 support library中的FragmentActivity的示例代碼: import android.support.annotation.NonNull; import android.support.annotation.Nullable; ... /** * Add support for inflating the &lt;fragment> tag. */ @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工具就會馬上把它標記為一個錯誤:
線程注解

@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' } 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM