來自谷歌官方文檔的翻譯。原文地址
使用Lint等工具進行代碼檢查可以幫助你查找問題改善代碼質(zhì)量雄妥,但是這些工具只能進行推斷并不能真正進行檢查。因為Android使用一個int
類型作為ID枝秤,表示字符串,圖片薇溃,顏色和其他類型的資源沐序,這時如果你在一個需要使用顏色資源ID的地方使用了一個字符串資源ID,代碼檢查工具是不會報錯的,但是你的應(yīng)用仍然會發(fā)生繪制錯誤或者是不能運行晶丘。
注解(Annotations)就是讓你給代碼檢查工具Lint等給一個提示,讓它們注意檢查這些微妙的代碼問題脑题。這些注解就好像元數(shù)據(jù)標(biāo)簽(mentadata tags)一樣和變量叔遂,參數(shù)蚕苇,返回值綁定在一起嚼吞,檢查它們是否合法舱禽。當(dāng)運行代碼檢查工具的時候誊稚,注解就會幫助你檢查比如空指針里伯,資源類型沖突等問題疾瓮。
Android通過Annotations Support Library支持多種注解,你可以通過android.support.annotation包獲取這些注解漫萄。
在工程中添加注解
在工程中打開注解功能,添加support-annotations
依賴。你添加的任何注解都會代碼檢查工具運行時或者lint task
時進行檢查启昧。
添加注解功能的工程依賴
Support Annotations library是Android Support Repository的一部分跛璧。所以你必須先下載support repository
追城,然后在build.gradle
中添加support-annotations
依賴。
-
在工具欄中點擊 SDK Manager
點擊 SDK Tools 標(biāo)簽.
展開 Support Repository, 并選中 Android Support Repository京髓。
點擊 OK.
安裝向?qū)е悬c擊 Continue 直到完成安裝.
-
將以下代碼添加到
build.gradle
文件中蛇摸,完成添加support-annotations
依賴:dependencies { compile 'com.android.support:support-annotations:24.2.0' }
這里使用的版本可能低于你下載的版本揽涮,所以這里指定的版本號必須和你在第三步中下載的版本號保持一致。
最后點擊工具欄或者通知中的 Sync Now。
如果你自己的庫模塊中使用了annotations,那么annotations就已經(jīng)以XML的方式存在于AAR(Android Archive (AAR) artifact)文件的annotations.zip
中了。使用了你的庫的用戶就沒有必要再以這種添加依賴的方式添加這個模塊了髓梅。
如果你想用Gradle Java plugin這種方式代替默認(rèn)的Android plugin for Gradle (com.android.application 或 com.android.library)
诡必,那你必須明確指定SDK庫的位置蟋字,因為Android支持庫并不支持JCenter
repositories {
jcenter()
maven { url '<your-SDK-path>/extras/android/m2repository' }
}
注意:如果你使用了appcompat庫涂炎,那你同樣不需要添加 support-annotations
依賴两蟀,因為 appcompat
庫已經(jīng)添加過這個依賴了蛀序,你可以直接使用annotations遣鼓。
Android支持庫中完整的注解列表你可以通過Support Annotations library reference查詢气笙,或者利用代碼補全功能,在輸入了import android.support.annotation.
語句后出現(xiàn)的可用項中查看堵第。
運行代碼檢查功能
在 Android Studio 的工具欄中選擇 Analyze > Inspect Code 胀瞪,啟動代碼檢查功能涵紊,包括確認(rèn)注解的有效性和Lint自動檢查兩部分既忆。Android Studio會顯示沖突信息宇挫,標(biāo)記出在代碼中潛藏的問題绘雁,并且給出相應(yīng)的解決解決建議。
你也可以用命令行啟動lint
任務(wù),這對持續(xù)集成服務(wù)器發(fā)現(xiàn)問題很有幫助,但是要注意,這樣啟動的lint
任務(wù)并不能檢查nullness
注解,只有Android Studio才能具備這個共軛能。關(guān)于Lint檢查的的問題,請看使用Lint改善你的代碼彩掐。
注意弹澎,即使注解沖突產(chǎn)生了警告佩迟,但是這些警告并不會組織代碼的編譯羔沙。
空值注解(Nullness Annotations)
@Nullable 注解表示變量诗充,參數(shù)茎匠,返回值可以為null
, @NonNull 注解表示變量谊惭,參數(shù)驱敲,返回值不能為null
。
如果一個值為null
的變量,被傳遞到了一個參數(shù)被標(biāo)記為@NonNull
的方法中厘托,這時編譯就會產(chǎn)生一個non-null
的警告包斑;另外一方面萌抵,如果引用一個返回值被標(biāo)記為@Nullable
的方法霎桅,并且你沒有對返回結(jié)果進行是否為空的檢查,那么就會受到一個nullness
的警告讨永。只有當(dāng)你想提醒方法的使用者滔驶,在每次使用方法前都要明確地進行非空檢查時,才能使用@Nullable
注解標(biāo)記方法的返回值住闯。
下面的例子使用了@NonNull
注解標(biāo)記了context
和parameters
兩個參數(shù)瓜浸,表示要檢查傳入的這兩個參數(shù)的值是不能為空,同時還要檢查onCreateView()
方法自身的返回值不能為空
import android.support.annotation.NonNull;
...
/** Add support for inflating the <fragment> tag. **/
@NonNull
@Override
public View onCreateView(String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
...
}
...
空值分析(Nullability Analysis)
Android Studio支持空值分析(nullability analysis)去自動推斷并且在代買中插入空值注解(nullness annotations)比原〔宸穑空值分析會掃描所有方法層次結(jié)構(gòu)中的調(diào)用關(guān)系,去檢查:
- 調(diào)用的方法可以返回空
- 調(diào)用的方法不能返回空
- 變量量窘,字段雇寇,局部變量,參數(shù)等可以為空
- 變量,字段锨侯,局部變量嫩海,參數(shù)等不能為空
分析完后會在檢查的位置自動插入適當(dāng)?shù)目罩底⒔狻?/p>
在Android Studio中選擇Analyze > Infer Nullity
開啟空值分析。Android Studio會在檢測的地方插入Android版本的 @Nullable 和 @NonNull 注解囚痴。下面是一些很好的實踐經(jīng)驗:
注意:當(dāng)加入空值注解的時候叁怪,代碼補全功能會建議我們使用 IntelliJ版本的 @Nullable and @NotNull 注解來代替Android版本的注解,同時也會自動引入相應(yīng)的包深滚。但是 Android Studio Lint 功能只會檢查Android版本的注解奕谭。當(dāng)確認(rèn)你的注解的時候,一定要檢查你的工程使用的是Android版本的注解痴荐,這樣Lint功能才能正常運行血柳。(PS:Android版本的是
@NonNull
,IntelliJ的是@NotNull
生兆;@Nullable
寫法是一樣的)
資源注解(Resource Annotations)
確認(rèn)資源類型非常有用难捌,因為Android對于資源的引用,比如drawable鸦难,string根吁,都是用整數(shù)類型進行傳遞的。如果代碼期待接收特定的資源類型明刷,比如Drawable婴栽,就可以把該資源引用的int值傳遞過去满粗。但是實際上也有可能錯傳了一個R.string
資源的int值過去辈末。所以確認(rèn)資源的類型很有用。
我們可以添加了一個@StringRes注解映皆,去檢查參數(shù)的是不是一個R.string
類型的資源:
public abstract void setTitle(@StringRes int resId) { … }
如果參數(shù)不是一個R.string
類型的資源挤聘,那么在代碼檢查期間就會產(chǎn)生一個警告捅彻。
其他[@DrawableRes][drawableres],[@DimenRes][dimenres],[@ColorRes][colorres],[@InterpolatorRes][interpolatorres]等資源注解都可以按這種格式使用步淹。如果你的參數(shù)支持多個資源格式缭裆,你可以對其添加多個資源注解澈驼。[@AnyRes][anyres]注解表示該菜蔬可以是任意一種R
資源格式。
即使你使用了[@ColorRes][colorres]指定了一個資源類型的參數(shù)徘六,但是用RRGGBB
或 AARRGGBB
表示的顏色整數(shù)值卻并不會被認(rèn)可。同樣用[@ColorInt][colorint]指定的資源也只認(rèn)可能被解析的顏色整數(shù)值炉擅。編譯工具會標(biāo)記出這些不正確的代碼谍失。
[stringres]:
[drawableres]: https://developer.android.com/reference/android/support/annotation/DrawableRes.html
[dimenres]:https://developer.android.com/reference/android/support/annotation/DimenRes.html
[colorres]:https://developer.android.com/reference/android/support/annotation/ColorRes.html
[interpolatorres]:https://developer.android.com/reference/android/support/annotation/InterpolatorRes.html
[anyres]:https://developer.android.com/reference/android/support/annotation/AnyRes.html
[colorint]:https://developer.android.com/reference/android/support/annotation/ColorInt.html
線程注解(Thread Annotations)
線程注解用來檢查方法是不是在一個特定的線程中被調(diào)用快鱼。支持以下注解
注意:編譯工具將
@MainThread
和@UiThread
看成是可互換的抹竹,所以你可以在標(biāo)注為@MainThread
的方法中調(diào)用@UiThread
的方法止潮,反之亦然喇闸。但是燃乍,在不同線程上有多個視圖的系統(tǒng)應(yīng)用程序的情況下刻蟹,UI線程可能不等同于主線程舆瘪。因此淀衣,您應(yīng)該使用@UiThread
注解與應(yīng)用程序視圖層次結(jié)構(gòu)相關(guān)的方法舌缤,并使用@MainThread
注解僅與應(yīng)用程序生命周期相關(guān)聯(lián)的方法国撵。
如果類中的所有方法都共享相同的線程介牙,則可以向類添加單個線程注解壮虫,以驗證類中的所有方法是否都從同一類型的線程中被調(diào)用。
線程注解的一個常見用法是驗證AsyncTask類中被覆蓋的方法环础,因為此類在后臺執(zhí)行,并僅在UI線程上發(fā)布結(jié)果线得。
值約束注解(Value Constraint Annotations)
使用@IntRange,@FloatRange和@Size注解驗證傳進來的參數(shù)的值贯钩。當(dāng)用戶可能輸入不在范圍內(nèi)的參數(shù)時募狂,@IntRange
和@FloatRange
非常有用角雷。
@IntRange
注解驗證整數(shù)或長整數(shù)參數(shù)的值是否在指定范圍內(nèi)祸穷。以下示例確保alpha
參數(shù)包含從0到255的整數(shù)值:
public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }
@FloatRange
注解檢查float
或double
類型參數(shù)值是否在浮點值的指定范圍內(nèi)勺三。以下示例確保alpha
參數(shù)包含從0.0到1.0的浮點值:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
@Size
注解檢查集合或數(shù)組的大小雷滚,以及字符串的長度祈远。 @Size
注解可用于驗證以下數(shù)量:
- 最大尺寸 (如 @Size(min=2))
- 最小尺寸 (如 @Size(max=2))
- 精確尺寸 (如 @Size(2))
- 尺寸的倍數(shù)(如 @Size(multiple=2))
例如炊汹,@Size(min=1)
檢查集合是否為空逃顶,@Size(3)
驗證數(shù)組是否包含有三個值以政。以下示例確保局部數(shù)組變量至少包含一個元素:
int[] location = new int[3];
button.getLocationOnScreen(@Size(min=1) location);
權(quán)限注解(Permission Annotations)
使用@RequiresPermission注解來驗證方法調(diào)用者的權(quán)限霸褒。要從列表中檢查單個權(quán)限的有效權(quán)限,請使用anyOf
屬性盈蛮。 要檢查一組權(quán)限废菱,請使用allOf
屬性。以下示例注解setWallpaper()
方法,以確保方法的調(diào)用者具有permission.SET_WALLPAPERS
權(quán)限:
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
此示例要求copyFile()
方法的調(diào)用者擁有讀寫外部存儲的權(quán)限:
@RequiresPermission(allOf = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE})
public static final void copyFile(String dest, String source) {
...
}
對于意圖(intents)的權(quán)限殊轴,將權(quán)限要求放置在定義意圖操作名稱的字符串字段上:
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
"android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
對于需要對讀寫訪問具有單獨權(quán)限的內(nèi)容提供程序(content providers)的權(quán)限衰倦,請在@RequiresPermission.Read 或 @RequiresPermission.Write注解中包含每個權(quán)限要求:
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
間接權(quán)限(Indirect Permissions)
當(dāng)權(quán)限取決于提供給方法參數(shù)的特定值時,只對該參數(shù)使用@RequiresPermission
注解旁理,而不用列出特定的權(quán)限樊零。 例如,startActivity(Intent) 方法對傳遞給方法的intent
參數(shù)就使用了間接權(quán)限:
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle) {...}
當(dāng)你使用間接權(quán)限時孽文,構(gòu)建工具會執(zhí)行數(shù)據(jù)流分析驻襟,以檢查傳遞給方法的參數(shù)是否有@RequiresPermission
注解。 然后他們從方法本身的參數(shù)強制執(zhí)行任何現(xiàn)有注解芋哭。 在startActivity(Intent)
示例中沉衣,當(dāng)沒有適當(dāng)權(quán)限的意圖傳遞給方法時,Intent類中的注解導(dǎo)致對startActivity(Intent)
的無效使用的結(jié)果警告减牺,如圖1所示厢蒜。
![](https://developer.android.com/studio/images/write/indirect-permissions-warning_2-2_2x.png)
構(gòu)建工具從Intent類中相應(yīng)意圖操作名稱上的注解在startActivity(Intent)
上生成警告:
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";
有時,您可以在注解方法的參數(shù)時烹植,用@RequiresPermission
替換@RequiresPermission.Read
和@RequiresPermission.Write
斑鸦。 但是,對于間接權(quán)限草雕,@RequiresPermission
不應(yīng)與讀取或?qū)懭霗?quán)限注解混合使用巷屿。
返回值注解(Return Value Annotations)
使用@CheckResult注解來驗證方法的結(jié)果或返回值實際上是否被使用。在容易混淆的方法結(jié)果上添加@CheckResult
注解用以區(qū)分墩虹,而不是對每個非void方法都進行注解嘱巾。在容易混淆的方法結(jié)果上添加@CheckResult
注解用以區(qū)分,而不是對每個非void方法都進行注解诫钓。 例如旬昭,Java開發(fā)?新手經(jīng)常錯誤地認(rèn)為<String>.trim()
是從原始字符串中刪除所有空格。對方法使用包含<String>.trim()
的@CheckResult
注解菌湃,?這樣調(diào)用者就不用對方法的返回值?進行任何操作了问拘。
以下示例中,@CheckResult
注解了[checkPermissions()](https://developer.android.com/reference/android/content/pm/PackageManager.html#checkPermission(java.lang.String, java.lang.String))方法惧所,以確保方法的返回值實際被引用骤坐。 它還建議開發(fā)人員將[enforcePermission()](https://developer.android.com/reference/android/content/ContextWrapper.html#enforcePermission(java.lang.String, int, int, java.lang.String))方法作為一種替代方案:
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
調(diào)用父類方法注解(CallSuper Annotations)
使用@CallSuper注解來驗證該方法是否調(diào)用了被覆蓋的父類?方法。以下示例中下愈,onCreate()
方法使用了該注解纽绍,以確保任何覆蓋了這個方法的實現(xiàn)都必須調(diào)用super.onCreate)
:
@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}
類型定義注解(Typedef Annotations)
你可以使用@IntDef和@StringDef注解,來創(chuàng)建一個整數(shù)或者字符串集合的枚舉類型势似。Typedef注解確保特定參數(shù)拌夏,返回值或字段只能使用特定的一組常量僧著。它們還可以使代碼擁有自動補全功能。
Typedef注解使用@interface
聲明新的枚舉注解類型障簿。@IntDef
和@StringDef
以及@Retention
注解一起標(biāo)注新的注解它盹愚,并且它們?nèi)齻€是定義一個枚舉類型所必需的。@Retention(RetentionPolicy.SOURCE)
注解告訴編譯器不要將被標(biāo)記了的枚舉類型數(shù)據(jù)存儲在.class
文件中卷谈。
以下示例說明了創(chuàng)建這種注解的步驟杯拐,以確保作為方法參數(shù)傳遞的值是一組已經(jīng)定義好的常量集合中的一個:
import android.support.annotation.IntDef;
...
public abstract class ActionBar {
...
// Define the list of accepted constants and declare the NavigationMode annotation
@Retention(RetentionPolicy.SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}
// Declare the constants
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
// Decorate the target methods with the annotation
@NavigationMode
public abstract int getNavigationMode();
// Attach the annotation
public abstract void setNavigationMode(@NavigationMode int mode);
如果mode
參數(shù)的值不是已經(jīng)定義的(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, or NAVIGATION_MODE_TABS)
其中的一個,編譯時就會受到警告世蔗。
你還可以將 @IntDef 和 @IntRange 一起使用端逼,用來限定一個整數(shù)值既是給定的一組常量中的一個,同時也是一個給定范圍內(nèi)的值污淋。
將常量和標(biāo)志位一起使用(Enable combining constants with flags)
如果用戶想將有效的常量與標(biāo)志位(例如|
, &
, ^
等)組合使用顶滩,你可以結(jié)合flag
屬性定義這個注解,以檢查參數(shù)或返回值是否是合法的樣式寸爆。以下示例用一系列DISPLAY_
常數(shù)創(chuàng)建了DisplayOptions
注解:
import android.support.annotation.IntDef;
...
@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 {}
...
當(dāng)你使用注解標(biāo)志構(gòu)建代碼時礁鲁,如果修飾的參數(shù)或返回值不是有效的樣式時,則會生成警告赁豆。
代碼訪問權(quán)限注解(Code Accessibility Annotations)
使用@VisibleForTesting和@Keep注解來表示方法仅醇,類或字段的可訪問性。
@VisibleForTesting
注解表示魔种,代碼測試時候析二,被注解了的這段代碼比其所聲明的,有更大的可見性节预。(比如聲明為private
叶摄,測試時就變成了public
)。
@Keep
注解確保被標(biāo)注的元素在編譯代碼中壓縮代碼資源的時候不會被刪除安拟。這個標(biāo)簽的典型應(yīng)用就是添加在要被反射調(diào)用(reflection)的類或者方法上面蛤吓,確保編譯器不會把這些方法或者類當(dāng)做是無用的資源而被優(yōu)化掉(刪除)。