1. 程式人生 > >使用註解改進程式碼檢查

使用註解改進程式碼檢查

使用程式碼檢查工具(例如 Lint)可以幫助您找到問題並改進程式碼,不過,檢查工具只能推斷這麼多資訊。例如,Android 資源 ID 使用 int 標識字串、圖形、顏色和其他資源型別,因此,檢查工具無法告訴您何時指定字串資源以及應在什麼地方指定顏色。在這種情況下,您的應用可能無法正確渲染或根本無法執行,即使使用程式碼檢查也是如此。

您可以使用註解向 Lint 之類的程式碼檢查工具提供提示,幫助檢測這些更細微的程式碼問題。您可以將註解作為元資料標記附加至變數、引數和返回值,用於檢查方法返回值、傳遞的引數以及本地變數和欄位。如果與程式碼檢查工具搭配使用,註解可以幫助您檢測問題,例如 null 指標異常和資源型別衝突。

向您的專案添加註解

要在您的專案中啟用註解,請向您的庫或應用新增 support-annotations 依賴項。新增的任何註解都會在您隨後執行程式碼檢查或 lint 任務時進行檢查。

新增支援註解庫依賴項

支援註解庫是 Android 支援庫的一部分。要向您的專案添加註解,您必須下載支援儲存庫並向 build.gradle檔案中新增 support-annotations 依賴項。

  1. 開啟 SDK 管理器,方法是點選工具欄中的 SDK Manager  或者選擇 Tools > Android > SDK Manager
  2. 點選 SDK Tools 標籤。
  3. 展開 Support Repository
     並選中 Android Support Repository 複選框。
  4. 點選 OK
  5. 繼續安裝嚮導的說明操作,安裝軟體包。
  6. 將以下程式碼行新增到 build.gradle 檔案的 dependencies 塊中,向您的專案新增 support-annotations 依賴項:
     dependencies { compile 'com.android.support:support-annotations:24.2.0'}
    您下載的庫版本可能較高,因此,確保您在此指定的值與第 3 步中的版本匹配。
  7. 在顯示的工具欄或同步通知中,點選 Sync Now

如果您在自己的庫模組中使用註解,註解將作為 Android 歸檔 (AAR) 工件的一部分以 XML 格式新增到 annotations.zip

 檔案中。新增 support-annotations 依賴項不會為您的庫的任何下游使用者引入依賴關係。

如果想要在未使用適用於 Gradle 的 Android 外掛但使用 Gradle Java 外掛的 Gradle 模組中使用註解(com.android.application 或 com.android.library),您必須明確新增 SDK 儲存庫,因為無法從 JCenter Java 儲存庫獲得 Android 支援庫:

repositories {
   jcenter()
   maven { url '<your-SDK-path>/extras/android/m2repository'}}

:如果您使用  庫,則無需新增 support-annotations 依賴項。因為 appcompat 庫已經依賴註解庫,您可以訪問相關注解。

要檢視支援儲存庫中包含的完整註解列表,請檢視支援註解庫參考,或者使用自動填充功能顯示 import android.support.annotation. 語句的可用選項。

執行程式碼檢查

要從 Android Studio 啟動程式碼檢查(包含驗證註解和自動 Lint 檢查),請從選單欄中選擇 Analyze > Inspect Code。Android Studio 將顯示衝突訊息,在您的程式碼與註解衝突的地方標記潛在問題並建議可能的解決方法。

您還可以通過使用命令列執行 lint 任務來強制註解。儘管這對標記持續整合伺服器遇到的問題可能有用,請注意,lint 任務並不會強制 nullness 註解(只有 Android Studio 會強制)。如需瞭解有關啟用和執行 Lint 檢查的詳細資訊,請參閱使用 Lint 改進您的程式碼

請注意,儘管註解衝突會生成警告,但這些警告不會阻止您的應用編譯。

Nullness 註解

新增  和  註解,以檢查給定變數、引數或返回值的 nullness。@Nullable 註解指示可以為 null 的變數、引數或返回值,而 @NonNull則指示不可為 null 的變數、引數或返回值。

例如,如果一個包含 null 值的區域性變數作為已附加 @NonNull 註解的引數傳遞到某個方法,則構建程式碼將生成一個指示非 null 衝突的警告。另一方面,對於通過 @Nullable 標記的方法的結果,如果不先檢查其是否為 null,那麼在嘗試引用它時將生成 nullness 警告。只有在每次使用方法時都應明確檢查是否為 null 的情況下,才應對方法返回值使用 @Nullable

下面的示例會將 @NonNull 註解附加到 context 與 attrs 引數,以檢查傳遞的引數值是否不為 null。它還會檢查 onCreateView() 方法本身是否不會返回 null:

import android.support.annotation.NonNull;.../** Add support for inflating the <fragment> tag. **/@NonNull@OverridepublicView onCreateView(String name,@NonNullContext context,@NonNullAttributeSet attrs){...}...

Nullability 分析

Android Studio 支援通過執行 nullability 分析,在您的程式碼中自動推斷和插入 nullness 註解。Nullability 分析會在您程式碼的整個方法層次結構中掃描協定類,以檢測:

  • 可返回 Null 的呼叫方法
  • 不會返回 Null 的方法
  • 可以為 Null 的變數,如欄位、區域性變數和引數
  • 不能為 Null 值的變數,如欄位、區域性變數和引數

然後,此分析將自動在已檢測到的位置插入相應的 null 註解。

要在 Android Studio 中執行 nullability 分析,請選擇 Analyze > Infer Nullity。Android Studio 會在程式碼中已檢測到的位置插入 Android  和  註解。執行 null 分析後,最好驗證一下插入的這些註解。

:新增 nullness 註解時,自動填充可能會建議 IntelliJ @Nullable 和 @NotNull 註解而不是 Android null 註解,並且可能會自動匯入相應的庫。不過,Android Studio Lint 檢查器僅會查詢 Android null 註解。驗證您的註解時,請確認您的專案使用 Android null 註解,以便 Lint 檢查器可以在程式碼檢查期間正確通知您。

資源註解

驗證資源型別可能非常有用,因為 Android 對資源(例如可繪製物件字串資源)的引用以整型形式傳遞。需要引數來引用特定型別資源(例如可繪製物件)的程式碼可以作為預計的引用型別 int 傳入,不過實際將引用不同型別的資源,例如 R.string 資源。

例如,新增  註解,以檢查資源引數是否包含 R.string 引用,如下面所示:

publicabstractvoid setTitle(@StringResint resId){}

在程式碼檢查期間,如果引數中未傳入 R.string 引用,註解將生成警告。

其他資源型別的註解(例如 、、 和 )可以使用相同的註解格式新增並在程式碼檢查期間執行。如果您的引數支援多種資源型別,您可以在給定引數上新增更多註解。使用  能夠指示註解的引數可為任意型別的 R 資源。

儘管您可以使用  指定某個引數應為顏色資源,但是顏色整型(RRGGBB 或 AARRGGBB 格式)無法識別為顏色資源。請改用  註解指示某個引數必須為顏色整型。構建工具會標記不正確程式碼,該程式碼會將顏色資源 ID(例如 android.R.color.black)而不是顏色整型傳遞到已註解方法。

執行緒註解

執行緒註解可以檢查某個方法是否從特定型別的執行緒呼叫。支援以下執行緒註解:

:構建工具會將 @MainThread 和 @UiThread 註解視為可互換,因此,您可以從 @MainThread 方法呼叫 @UiThread 方法,反之亦然。不過,如果系統應用在不同執行緒上帶有多個檢視,UI 執行緒可與主執行緒不同。因此,您應使用 @UiThread 標註與應用的檢視層次結構關聯的方法,使用 @MainThread 僅標註與應用生命週期關聯的方法。

如果某個類中的所有方法具有相同的執行緒要求,您可以向該類新增一個執行緒註解,以驗證該類中的所有方法是否均從相同型別的執行緒呼叫。

執行緒註解的一個常見用途是驗證 AsyncTask 類中的方法替換,因為此類會執行後臺操作並將結果僅釋出到 UI 執行緒上。

值約束註解

使用 、 和 @Size 註解可以驗證傳遞的引數的值。在應用到使用者可能弄錯其範圍的引數時,@IntRange 和 @FloatRange 都非常有用。

@IntRange 註解可以驗證整型或長整型引數值是否位於指定範圍內。下面的示例可以確保 alpha 引數包含 0 至 255 範圍內的整數值:

publicvoid setAlpha(@IntRange(from=0,to=255)int alpha){}

@FloatRange 註解可以檢查浮點或雙整型引數值是否位於指定的浮點值範圍內。下面的示例可以確保 alpha 引數包含 0.0 至 1.0 的浮點值:

publicvoid 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 陣列至少包含一個元素:
int[] location =newint[3];
button.getLocationOnScreen(@Size(min=1) location);

許可權註解

使用  註解可以驗證方法呼叫方的許可權。要檢查有效許可權列表中是否存在某個許可權,請使用 anyOf 屬性。要檢查是否存在一組許可權,請使用 allOf 屬性。下面的示例會標註 setWallpaper() 方法,以確保方法的呼叫方擁有 permission.SET_WALLPAPERS 許可權:

@RequiresPermission(Manifest.permission.SET_WALLPAPER)publicabstractvoid setWallpaper(Bitmap bitmap)throwsIOException;

此示例要求 copyFile() 方法的呼叫方同時具有外部儲存空間的讀寫許可權:

@RequiresPermission(allOf ={Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE})publicstaticfinalvoid copyFile(String dest,String source){...}

對於 intent 許可權,請將許可權要求新增到定義 intent 操作名稱的字串欄位上:

@RequiresPermission(android.Manifest.permission.BLUETOOTH)publicstaticfinalString ACTION_REQUEST_DISCOVERABLE ="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))publicstaticfinalUri BOOKMARKS_URI =Uri.parse("content://browser/bookmarks");

間接許可權

如果許可權依賴於提供給方法引數的特定值,請對引數本身使用 @RequiresPermission,而不用列出具體許可權。例如, 方法會對傳遞到方法的 intent 使用間接許可權:

publicabstractvoid startActivity(@RequiresPermissionIntent intent,@NullableBundle){...}

在您使用間接許可權時,構建工具將執行資料流分析以檢查傳遞到方法的引數是否具有任何 @RequiresPermission 註解。隨後,它們會對方法本身強制引數的任何現有註解。在 startActivity(Intent) 示例中,當一個不具有相應許可權的 intent 傳遞到方法時, 類中的註解會針對 startActivity(Intent) 的無效使用生成警告,如圖 1 中所示。

圖 1. startActivity(Intent) 方法上從間接許可權註解生成的警告。

構建工具會在 startActivity(Intent) 上從  類中相應 intent 操作名稱的註解生成警告:

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)@RequiresPermission(Manifest.permission.CALL_PHONE)publicstaticfinalString ACTION_CALL ="android.intent.action.CALL";

如果需要,在標註方法的引數時,您可以將 @RequiresPermission 替換為 @RequiresPermission.Read 和/或 @RequiresPermission.Write。不過,間接許可權 @RequiresPermission 不應與讀取或寫入許可權註解搭配使用。

返回值註解

使用  註解可以驗證實際使用的是方法的結果還是返回值。添加註釋來闡明可能令人困惑的方法的結果,而不是使用 @CheckResult 標註每個非空方法。例如,新 Java 開發者經常誤認為 <String>.trim() 會移除原始字串中的空格。使用 @CheckResult 標註方法會在呼叫方未對方法返回值作任何處理的地方標記 <String>.trim() 的使用。

@CheckResult(suggest="#enforcePermission(String,int,int,String)")publicabstractint checkPermission(@NonNullString permission,int pid,int uid);

CallSuper 註解

使用  註解可以驗證替換方法是否會呼叫方法的超類實現。下面的示例會標註 onCreate() 方法,以確保任何替換方法實現都會呼叫 super.onCreate()

@CallSuperprotectedvoid onCreate(Bundle savedInstanceState){}

Typedef 註解

使用  和  註解,以便能夠建立整型和字串集的列舉註解來驗證其他型別的程式碼引用。Typedef 註解可以確保特定引數、返回值或欄位引用特定的常量集。它們還可以完成程式碼以自動提供允許的常量。

Typedef 註解使用 @interface 宣告新的列舉註解型別。@IntDef 和 @StringDef 註解以及 @Retention 可以標註新註解,並且為定義列舉的型別所必需。@Retention(RetentionPolicy.SOURCE) 註解可以告知編譯器不將列舉的註解資料儲存在 .class 檔案中。

下面的示例說明了建立註解的具體步驟,此註解可以確保作為方法引數傳遞的值引用一個定義的常量:

import android.support.annotation.IntDef;...publicabstractclassActionBar{...// 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@interfaceNavigationMode{}// Declare the constantspublicstaticfinalint NAVIGATION_MODE_STANDARD =0;publicstaticfinalint NAVIGATION_MODE_LIST =1;publicstaticfinalint NAVIGATION_MODE_TABS =2;// Decorate the target methods with the annotation@NavigationModepublicabstractint getNavigationMode();// Attach the annotationpublicabstractvoid setNavigationMode(@NavigationModeint mode);

在您構建此程式碼時,如果 mode 引數不引用一個定義的常量(NAVIGATION_MODE_STANDARDNAVIGATION_MODE_LIST 或 NAVIGATION_MODE_TABS),則會生成警告。

您還可以組合  和 ,以指示整型可以是給定的常量集或某個範圍內的值。

允許將常量與標誌相結合

如果使用者可以將允許的常量與標誌(例如,|& 和 ^,等等)相結合,則您可以通過 flag 屬性定義一個註解,以檢查某個引數或返回值是否會引用有效模式。下面的示例將使用一組有效的 DISPLAY_ 常量建立 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@interfaceDisplayOptions{}...

在您使用註解標誌構建程式碼時,如果經過修飾的引數或返回值不引用有效模式,則將生成警告。

程式碼可訪問性註解

使用  和 @Keep 註解可以表示方法、類或欄位的可訪問性。

@VisibleForTesting 註解指示一個程式碼塊的可見性是否高於讓程式碼變得可測試所需要的水平。

@Keep 註解可以確保如果在構建時縮減程式碼,標註的元素不會移除。它一般會新增到通過反射訪問的方法和類中,以阻止編譯器將程式碼視為未使用。