使用 Lint 等程式碼檢查工具可協助您找出問題並改善程式碼,但檢查工具不能推論出所有問題。舉例來說,Android 資源 ID 會使用 int
來判定字串、圖形、顏色和其他資源類型,因此檢查工具並無法判斷您是否在應該指定顏色的地方,指定了字串資源。在這種情況下,即使您使用程式碼檢查功能,應用程式仍可能算繪錯誤或完全無法運作。
註解讓使用者可以對 Lint 等檢查工具進行微調,藉此偵測出這種較細微的程式碼問題。系統會將這些註解新增為中繼資料標記,您可以附加到變數、參數和回傳值,以檢查方法回傳值、傳遞的參數、本機變數和各項欄位。與程式碼檢查工具搭配使用時,註解可協助您偵測問題,例如空值指標例外狀況和資源類型衝突。
Android 透過註解支援資料庫支援各種註解。您可以透過 android.support.annotation
套件存取這個資料庫。
注意:假使模組在註解處理工具中有依附元件,必須使用「annotationProcessor」依附元件設定才能新增該元件。 詳情請參閱使用註解處理工具依附元件設定。
新增專案註解
如要在專案中啟用註解,請將 support-annotations
依附元件新增至程式庫或應用程式。在您執行程式碼檢查或 lint
工作時,系統會檢查您新增的所有註解。
新增支援註解程式庫依附元件
「支援註解」程式庫已發布在 Google 的 Maven 存放區中。如要將支援註解程式庫新增至專案,請在 build.gradle
檔案的 dependencies
區塊中加入下列程式碼:
Groovy
dependencies { implementation 'com.android.support:support-annotations:28.0.0' }
Kotlin
dependencies { implementation("com.android.support:support-annotations:28.0.0") }
假使您在自己的程式庫模組中使用註解,則註解會在 annotations.zip
檔案中以 XML 格式包含在 Android ARchive (AAR) 構件內。加入 support-annotations
依附元件,並不會使程式庫下游使用者的依附元件增加。
注意:假使您使用的是 appcompat
程式庫,則不必新增 support-annotations
依附元件。由於 appcompat
程式庫已經與註解程式庫存在依附關係,因此您可以存取註解。
如需支援存放區內註解的完整清單,請參閱支援註解程式庫參考資料,或使用自動完成功能查看可用的 import android.support.annotation.
陳述式選項。
執行程式碼檢查
如要透過 Android Studio 啟動程式碼檢查作業,包括驗證註解和自動 Lint 檢查,請從選單列依序選取「分析」>「檢查程式碼」。如果程式碼與註解發生衝突,Android Studio 會顯示衝突訊息,以標明潛在問題,並建議可行的解決方式。
您也可以使用指令列執行 lint
工作,強制執行註解。雖然這有助找出持續整合伺服器的問題,但 lint
工作不會強制執行空值註解,只有 Android Studio 才會執行。如要進一步瞭解如何啟用及執行 Lint 檢查,請參閱使用 Lint 改善程式碼。
請注意,雖然註解衝突會產生警示,但這些警示並不會妨礙應用程式編譯。
空值註解
新增 @Nullable
和 @NonNull
註解,檢查特定變數、參數或傳回值的空值。@Nullable
註解代表可以是空值的變數、參數或傳回值,而 @NonNull
則代表不能是空值的變數、參數或傳回值。
舉例來說,如果您將包含空值的本機變數當做參數傳至方法,且該參數帶有 @NonNull
註解,則建構程式碼時,系統會產生警示,標示出非空值衝突。另一方面,在未檢查結果是否為空值的情況下,嘗試對標有 @Nullable
的方法參照結果,則會使系統產生空值警示。只有在每次使用該方法都要明確執行空值檢查時,才應將 @Nullable
用於方法的回傳值。
下列範例會將 @NonNull
註解附加至 context
和 attrs
參數,以確認傳遞的參數值並非空值,也會檢查 onCreateView()
方法本身不會傳回空值。請注意,使用 Kotlin 時,不需要使用 @NonNull
註解,因為當系統指定不可為空值的類型時,註解即會自動新增至產生的位元碼:
Kotlin
import android.support.annotation.NonNull ... /** Add support for inflating the <fragment> tag. **/ fun onCreateView( name: String?, context: Context, attrs: AttributeSet ): View? { ... } ...
Java
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) { ... } ...
分析是否可為空值
Android Studio 能執行是否可為空值的分析,以在程式碼中自動推論並插入空值註解。這項分析會掃描程式碼中所有方法階層的契約,以偵測下列項目:
- 可以傳回空值的呼叫方法
- 不應傳回空值的方法
- 可以是空值的變數,例如欄位、本機變數和參數
- 不能是空值的變數,例如欄位、本機變數和參數
分析工具隨後會在偵測到上述項目的位置,自動插入適當的空值註解。
如要在 Android Studio 中執行空值分析,請選取「分析」>「Infer Nullity」。Android Studio 會將 Android @Nullable
和 @NonNull
註解插入在程式碼中偵測到上述項目的位置。執行空值分析後,建議您驗證插入的註解。
注意:新增空值註解時,自動完成功能可能會建議 IntelliJ @Nullable
和 @NotNull
註解,而非 Android 空值註解,並可能會自動匯入相應的程式庫。然而,Android Studio Lint 檢查工具僅會尋找 Android 空值的註解。驗證註解時,請確認專案使用的是 Android 空值註解,這樣 Lint 檢查工具才能確實在檢查程式碼時傳送通知給您。
資源註解
驗證資源類型很有幫助,因為 Android 參照可繪項目和字串等資源後,系統會將這些參照項目當做整數傳遞。
如果程式碼預期參數會參照特定的資源類型 (例如可繪項目),則系統可能會傳遞預期中的 int
參照類型,但該程式碼實際上仍可參照不同類型的資源,例如 R.string
資源。
舉例來說,請加入 @StringRes
註解來檢查資源參數是否包含 R.string
參照,如下所示:
Kotlin
abstract fun setTitle(@StringRes resId: Int)
Java
public abstract void setTitle(@StringRes int resId)
在程式碼檢查期間,假使參數未傳遞 R.string
參照資料,註解即會產生警示。
@DrawableRes
、@DimenRes
、@ColorRes
和 @InterpolatorRes
等其他資源類型的註解,也可使用相同的註解格式新增,並會在程式碼檢查期間執行。假使參數支援多種資源類型,您可以在當中加入多個註解。您可以使用 @AnyRes
,標明附有註解的參數可以是任何類型的 R
資源。
雖然您可以使用 @ColorRes
指定某參數應為顏色資源,但系統不會將 RRGGBB
或 AARRGGBB
格式的顏色整數視為顏色資源。請改用 @ColorInt
註解來表明參數必須是顏色整數。如果不正確的程式碼將 android.R.color.black
這類的顏色資源 ID 傳遞至附有註解的方法,而不是傳遞顏色整數,建構工具就會標記該程式碼。
執行緒註解
執行緒註解會檢查方法是不是從特定類型的執行緒呼叫。系統支援的執行緒註解如下:
注意:建構工具會將 @MainThread
和 @UiThread
註解視為可互換,因此您可以透過 @MainThread
方法呼叫 @UiThread
方法,反之亦然。不過,如果是在各執行緒檢視畫面不同的系統應用程式當中,UI 執行緒和主要執行緒則可能不同。因此,對於和應用程式檢視區塊階層關聯的方法,您應使用 @UiThread
來做為註解,至於 @MainThread
註解則僅應用於和應用程式生命週期關聯的方法。
假使類別中所有方法都具有相同的執行緒要求,您可以在類別中新增單一執行緒註解,以驗證類別中的所有方法都是透過相同執行緒來呼叫。
執行緒註解的常見用途是驗證 AsyncTask 類別的方法覆寫作業,因為此類別會執行背景作業,然後僅將結果發布於 UI 執行緒。
值限制註解
使用 @IntRange
、@FloatRange
和 @Size
註解來驗證傳遞的參數值。對使用者可能有範圍錯誤的參數套用時,@IntRange
和 @FloatRange
最為有用。
@IntRange
註解會驗證整數或長參數值是否在指定範圍內。下列範例會確保 alpha
參數包含 0 到 255 之間的整數值:
Kotlin
fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { ... }
Java
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }
@FloatRange
註解會檢查浮點數或雙參數值是否在浮點值範圍內。下列範例可確保 alpha
參數包含介於 0.0 至 1.0 的浮點值:
Kotlin
fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}
Java
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
@Size
註解會檢查集合或陣列的大小,以及字串長度。@Size
註解可用來驗證下列特質:
- 最小尺寸 (例如
@Size(min=2)
) - 最大尺寸 (例如
@Size(max=2)
) - 實際大小 (例如
@Size(2)
) - 數字的倍數必須是一個數字 (例如
@Size(multiple=2)
)
例如,@Size(min=1)
會檢查集合是否並非空白,@Size(3)
則會驗證該陣列僅包含三個值。下列範例可確保 location
陣列包含至少一個元素:
Kotlin
fun getLocation(button: View, @Size(min=1) location: IntArray) { button.getLocationOnScreen(location) }
Java
void getLocation(View button, @Size(min=1) int[] location) { button.getLocationOnScreen(location); }
權限註解
使用 @RequiresPermission
註解來驗證方法呼叫端的權限。如要從有效權限清單中檢視單一權限,請使用 anyOf
屬性。如要檢查一組權限,請使用 allOf
屬性。下列範例會註解 setWallpaper()
方法,確保該方法的調用者擁有 permission.SET_WALLPAPERS
權限:
Kotlin
@RequiresPermission(Manifest.permission.SET_WALLPAPER) @Throws(IOException::class) abstract fun setWallpaper(bitmap: Bitmap)
Java
@RequiresPermission(Manifest.permission.SET_WALLPAPER) public abstract void setWallpaper(Bitmap bitmap) throws IOException;
此範例要求 copyImageFile()
方法的調用者同時擁有外部儲存空間的讀取權限,以及複製映像檔中的地點中繼資料讀取權限:
Kotlin
@RequiresPermission(allOf = [ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION ]) fun copyImageFile(dest: String, source: String) { ... }
Java
@RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION}) public static final void copyImageFile(String dest, String source) { //... }
如要取得意圖的權限,請在定義意圖動作名稱的字串欄位中加入權限要求:
Kotlin
@RequiresPermission(android.Manifest.permission.BLUETOOTH) const val ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
Java
@RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
對於需要特定讀取與寫入權限的內容供應器權限,請在 @RequiresPermission.Read
或 @RequiresPermission.Write
註解中納入各項權限要求:
Kotlin
@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS)) val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")
Java
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
間接權限
假使權限取決於提供至參數的特定值,請在參數本身上使用 @RequiresPermission
,而不列出特定權限。舉例來說,
startActivity(Intent)
方法會在傳遞至方法的意圖上使用間接權限:
Kotlin
abstract fun startActivity(@RequiresPermission intent: Intent, bundle: Bundle?)
Java
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)
使用間接權限時,建構工具會執行資料流程分析,檢查傳遞至方法的引數是否有任何 @RequiresPermission
註解。然後強制執行方法參數中現有的任何註解。在 startActivity(Intent)
範例中,假使將沒有適當權限的意圖傳遞至方法,Intent
類別中的註解會導致 startActivity(Intent)
出現無效警示,如圖 1 所示

圖 1. 透過 startActivity(Intent)
方法的間接權限註解產生的警示。
建構工具針對 Intent
類別中相應意圖動作名稱的註解,在 startActivity(Intent)
上產生警示:
Kotlin
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @RequiresPermission(Manifest.permission.CALL_PHONE) const val ACTION_CALL = "android.intent.action.CALL"
Java
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @RequiresPermission(Manifest.permission.CALL_PHONE) public static final String ACTION_CALL = "android.intent.action.CALL";
如有需要,您可以在為方法的參數註解時,將 @RequiresPermission.Read
和/或 @RequiresPermission.Write
換成 @RequiresPermission
。然而,假使是間接權限,@RequiresPermission
不得與讀取或寫入權限註解搭配使用。
傳回值註解
使用 @CheckResult
註解來驗證方法的結果或傳回值是否確實被使用。請勿透過 @CheckResult
為各個非空值的方法加上註解,請新增註解來釐清可能混淆方法的結果。例如,新的 Java 開發人員通常會誤將 <String>.trim()
從原始字串中移除空白字元。使用 @CheckResult
旗標標記方法時,會使用 <String>.trim()
作為調用方式,因為調用者不會使用方法的傳回值執行任何操作。
下列範例會註解 checkPermissions()
方法,確保系統確實參照了方法的傳回值。而且會將 enforcePermission()
方法命名為為開發人員建議的替代方法:
Kotlin
@CheckResult(suggest = "#enforcePermission(String,int,int,String)") abstract fun checkPermission(permission: String, pid: Int, uid: Int): Int
Java
@CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String permission, int pid, int uid);
CallSuper 註解
使用 @CallSuper
註解來驗證覆寫方法是否調用了該方法的超級實作。下列範例會加上註解 onCreate()
方法,確保任何覆寫的方法實作項目會調用 super.onCreate()
:
Kotlin
@CallSuper override fun onCreate(savedInstanceState: Bundle?) { }
Java
@CallSuper protected void onCreate(Bundle savedInstanceState) { }
Typedef 註解
使用 @IntDef
和 @StringDef
註解,您可以建立整數和字串集的列舉註解,以驗證其他類型的程式碼參照。Typedef 註解可確保特定參數、傳回值或欄位參照了一組特定常數。也能啟用程式碼,自動提供允許的常數。
Typedef 註解會使用 @interface
來宣告新的列舉註解類型。@IntDef
和 @StringDef
註解以及 @Retention
註解新註解是定義列舉類型的必要項目。@Retention(RetentionPolicy.SOURCE)
註解會告知編譯器不要將列舉的註解資料儲存在 .class
檔案中。
下列範例說明如何建立註解,確保作為方法參數傳遞的值會參照其中一種定義的常數:
Kotlin
import android.support.annotation.IntDef //... // Define the list of accepted constants and declare the NavigationMode annotation @Retention(AnnotationRetention.SOURCE) @IntDef(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS) annotation class NavigationMode // Declare the constants const val NAVIGATION_MODE_STANDARD = 0 const val NAVIGATION_MODE_LIST = 1 const val NAVIGATION_MODE_TABS = 2 abstract class ActionBar { // Decorate the target methods with the annotation // Attach the annotation @get:NavigationMode @setparam:NavigationMode abstract var navigationMode: Int }
Java
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
參數未參照任何定義的常數 (NAVIGATION_MODE_STANDARD
、NAVIGATION_MODE_LIST
或 NAVIGATION_MODE_TABS
),即會產生警示。
您也可以將 @IntDef
和 @IntRange
結合,表明整數可以是固定的一組常數或範圍內的值。
啟用將常數與旗標結合的功能
假使使用者可以將常數與旗標 (例如 |
、&
、^
等) 合併,則可使用 flag
定義註解屬性來檢查參數或傳回值是否參照有效的模式。下列範例使用有效的 DISPLAY_
常數清單來建立 DisplayOptions
註解:
Kotlin
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(AnnotationRetention.SOURCE) annotation class DisplayOptions ...
Java
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 {} ...
使用註解標記建構程式碼時,假使裝飾的參數或傳回值未參照有效的模式,即會產生警示。
保留註解
@Keep
註解可確保在建構時縮小程式碼不會移除帶註釋的類別或方法。這類註解通常會加入因反映而存取的方法和類別,以防止編譯器將程式碼視為未使用。
注意:使用 @Keep
註解的類別和方法一律會出現在應用程式 APK 中,即使您從未在應用程式的邏輯中參照這些類別和方法。
為了讓應用程式大小保持較小,請考慮是否有必要保留應用程式中的各個 @Keep
註解。假使您是透過反映來存取加註的類別或方法,請在 ProGuard 規則中使用 -if
條件,指定反映此回應的類別調用。
如要進一步瞭解如何壓縮程式碼,以及指定不應移除的程式碼,請參閱縮減程式碼和資源。
程式碼瀏覽權限註解
使用下列註解來表明程式碼的特定部分 (例如方法、類別、欄位或套件) 的瀏覽權限。
公開測試
@VisibleForTesting
註解表明註解方法比使該方法可測試的通常所需更明顯。此註解包含選用的 otherwise
引數,可讓您指定是否要讓方法顯示,而不開放測試。Lint 使用 otherwise
引數來強制執行預期的瀏覽權限。
在下列範例中,myMethod()
通常為 private
,但這是不公開的測試用套件。透過下列 VisibleForTesting.PRIVATE
標示,假使從 private
存取權允許的內容之外 (例如其他編譯單位) 調用此方法,ML 會顯示訊息。
Kotlin
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun myMethod() { ... }
Java
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) void myMethod() { ... }
您也可以指定 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
,表明方法僅用於測試。這份表單與使用 @RestrictTo(TESTS)
相同。都能執行相同的 Lint 檢查。
限制 API
@RestrictTo
註解代表有註解 API (套件、類別或方法) 的存取權有限,如下所示。
子類別
您可以使用註解表單 @RestrictTo(RestrictTo.Scope.SUBCLASSES)
,限定 API 僅能存取子類別。
僅有擴充已加註類別的類別才能存取此 API。Java protected
輔助鍵不夠嚴格,因為這樣可從同一套件中的不相關類別存取。此外,有時您可能想保留方法 public
以提高彈性,因為您無法一律使用先前的 protected
和覆寫方法 public
,但仍可提供提示該類別僅適用於類別或子類別的用法。
程式庫
您可以使用註解表單 @RestrictTo(RestrictTo.Scope.GROUP_ID)
,限制 API 僅能存取程式庫。
僅有程式庫程式碼才能存取註解的 API。因此,您可以在任何套件階層中整理程式碼,同時在一組相關程式庫中共用程式碼。支援程式庫中有很多選項,而該程式庫中有許多導入程式碼,但通常不會供外部使用,但必須為 public
,才能將這些意見分享給各個補充支援程式庫。
注意:此 Android 訂閱項目程式庫類別和套件現已加註 @RestrictTo(GROUP_ID)
,也就是說,假使不小心使用這些實作類別,Lint 會警告您不建議這麼做。
測試
您可以使用註解表單 @RestrictTo(RestrictTo.Scope.TESTS)
,避免其他開發人員存取您的測試 API。
僅有測試程式碼才能存取加註的 API。這樣可以避免其他開發人員使用 API 進行您打算僅用於測試目的的開發。