[官方文檔]使用注解改進(jìn)代碼檢查

原文地址:https://developer.android.com/studio/write/annotations#thread-annotations

使用代碼檢查工具(例如 Lint)可以幫助您找到問題并改進(jìn)代碼,不過,檢查工具只能推斷這么多信息。例如,Android 資源 ID 使用 int 標(biāo)識(shí)字符串、圖形、顏色和其他資源類型,因此,檢查工具無法告訴您何時(shí)指定字符串資源以及應(yīng)在什么地方指定顏色。在這種情況下,您的應(yīng)用可能無法正確渲染或根本無法運(yùn)行,即使使用代碼檢查也是如此。

您可以使用注解向 Lint 之類的代碼檢查工具提供提示,幫助檢測(cè)這些更細(xì)微的代碼問題。您可以將注解作為元數(shù)據(jù)標(biāo)記附加至變量、參數(shù)和返回值,用于檢查方法返回值、傳遞的參數(shù)以及本地變量和字段。如果與代碼檢查工具搭配使用,注解可以幫助您檢測(cè)問題,例如 null 指針異常和資源類型沖突。

Android 通過注解支持庫支持各種注解。您可以通過 android.support.annotation 軟件包獲取該庫。

向您的項(xiàng)目添加注解

要在您的項(xiàng)目中啟用注解,請(qǐng)向您的庫或應(yīng)用添加 support-annotations 依賴項(xiàng)。添加的任何注解都會(huì)在您隨后運(yùn)行代碼檢查或 lint 任務(wù)時(shí)進(jìn)行檢查。

添加支持注解庫依賴項(xiàng)

支持注解庫是 Android 支持庫的一部分。要向您的項(xiàng)目添加注解,您必須下載支持存儲(chǔ)庫并向 build.gradle 文件中添加 support-annotations 依賴項(xiàng)。

  1. 打開 SDK 管理器,方法是點(diǎn)擊工具欄中的 SDK Manager 或者選擇 Tools > Android > SDK Manager。
  2. 點(diǎn)擊 SDK Tools 標(biāo)簽。
  3. 展開 Support Repository 并選中 Android Support Repository 復(fù)選框。
  4. 點(diǎn)擊 OK。
  5. 繼續(xù)安裝向?qū)У恼f明操作,安裝軟件包。
  6. 將以下代碼行添加到 build.gradle 文件的 dependencies 塊中,向您的項(xiàng)目添加 support-annotations 依賴項(xiàng):
dependencies { compile 'com.android.support:support-annotations:24.2.0' } 

您下載的庫版本可能較高,因此,確保您在此指定的值與第 3 步中的版本匹配。

  1. 在顯示的工具欄或同步通知中,點(diǎn)擊 Sync Now

如果您在自己的庫模塊中使用注解,注解將作為 Android 歸檔 (AAR) 工件的一部分以 XML 格式添加到 annotations.zip文件中。添加 support-annotations 依賴項(xiàng)不會(huì)為您的庫的任何下游用戶引入依賴關(guān)系。

如果想要在未使用適用于 Gradle 的 Android 插件但使用 Gradle Java 插件的 Gradle 模塊中使用注解(com.android.applicationcom.android.library),您必須明確添加 SDK 存儲(chǔ)庫,因?yàn)闊o法從 JCenter Java 存儲(chǔ)庫獲得 Android 支持庫:

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

:如果您使用 appcompat 庫,則無需添加 support-annotations 依賴項(xiàng)。因?yàn)?appcompat 庫已經(jīng)依賴注解庫,您可以訪問相關(guān)注解。

要查看支持存儲(chǔ)庫中包含的完整注解列表,請(qǐng)查看支持注解庫參考,或者使用自動(dòng)填充功能顯示 import android.support.annotation. 語句的可用選項(xiàng)。

運(yùn)行代碼檢查

要從 Android Studio 啟動(dòng)代碼檢查(包含驗(yàn)證注解和自動(dòng) Lint 檢查),請(qǐng)從菜單欄中選擇 Analyze > Inspect Code。Android Studio 將顯示沖突消息,在您的代碼與注解沖突的地方標(biāo)記潛在問題并建議可能的解決方法。

您還可以通過使用命令行運(yùn)行 lint 任務(wù)來強(qiáng)制注解。盡管這對(duì)標(biāo)記持續(xù)集成服務(wù)器遇到的問題可能有用,請(qǐng)注意,lint 任務(wù)并不會(huì)強(qiáng)制 nullness 注解(只有 Android Studio 會(huì)強(qiáng)制)。如需了解有關(guān)啟用和運(yùn)行 Lint 檢查的詳細(xì)信息,請(qǐng)參閱使用 Lint 改進(jìn)您的代碼。

請(qǐng)注意,盡管注解沖突會(huì)生成警告,但這些警告不會(huì)阻止您的應(yīng)用編譯。

Nullness 注解

添加 @Nullable@NonNull注解,以檢查給定變量、參數(shù)或返回值的 nullness。@Nullable 注解指示可以為 null 的變量、參數(shù)或返回值,而 @NonNull 則指示不可為 null 的變量、參數(shù)或返回值。

例如,如果一個(gè)包含 null 值的局部變量作為已附加 @NonNull 注解的參數(shù)傳遞到某個(gè)方法,則構(gòu)建代碼將生成一個(gè)指示非 null 沖突的警告。另一方面,對(duì)于通過 @Nullable 標(biāo)記的方法的結(jié)果,如果不先檢查其是否為 null,那么在嘗試引用它時(shí)將生成 nullness 警告。只有在每次使用方法時(shí)都應(yīng)明確檢查是否為 null 的情況下,才應(yīng)對(duì)方法返回值使用 @Nullable。

下面的示例會(huì)將 @NonNull 注解附加到 contextattrs 參數(shù),以檢查傳遞的參數(shù)值是否不為 null。它還會(huì)檢查 onCreateView() 方法本身是否不會(huì)返回 null:

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 分析

Android Studio 支持通過運(yùn)行 nullability 分析,在您的代碼中自動(dòng)推斷和插入 nullness 注解。Nullability 分析會(huì)在您代碼的整個(gè)方法層次結(jié)構(gòu)中掃描協(xié)定類,以檢測(cè):

  • 可返回 Null 的調(diào)用方法
  • 不會(huì)返回 Null 的方法
  • 可以為 Null 的變量,如字段、局部變量和參數(shù)
  • 不能為 Null 值的變量,如字段、局部變量和參數(shù)

然后,此分析將自動(dòng)在已檢測(cè)到的位置插入相應(yīng)的 null 注解。

要在 Android Studio 中運(yùn)行 nullability 分析,請(qǐng)選擇 Analyze > Infer Nullity。Android Studio 會(huì)在代碼中已檢測(cè)到的位置插入 Android @Nullable@NonNull 注解。運(yùn)行 null 分析后,最好驗(yàn)證一下插入的這些注解。

:添加 nullness 注解時(shí),自動(dòng)填充可能會(huì)建議 IntelliJ @Nullable@NotNull 注解而不是 Android null 注解,并且可能會(huì)自動(dòng)導(dǎo)入相應(yīng)的庫。不過,Android Studio Lint 檢查器僅會(huì)查找 Android null 注解。驗(yàn)證您的注解時(shí),請(qǐng)確認(rèn)您的項(xiàng)目使用 Android null 注解,以便 Lint 檢查器可以在代碼檢查期間正確通知您。

資源注解

驗(yàn)證資源類型可能非常有用,因?yàn)?Android 對(duì)資源(例如可繪制對(duì)象字符串資源)的引用以整型形式傳遞。需要參數(shù)來引用特定類型資源(例如可繪制對(duì)象)的代碼可以作為預(yù)計(jì)的引用類型 int 傳入,不過實(shí)際將引用不同類型的資源,例如 R.string 資源。

例如,添加 @StringRes注解,以檢查資源參數(shù)是否包含 R.string 引用,如下面所示:

public abstract void setTitle(@StringRes int resId) { … }

在代碼檢查期間,如果參數(shù)中未傳入 R.string 引用,注解將生成警告。

其他資源類型的注解(例如 @DrawableRes、@DimenRes、@ColorRes@InterpolatorRes)可以使用相同的注解格式添加并在代碼檢查期間運(yùn)行。如果您的參數(shù)支持多種資源類型,您可以在給定參數(shù)上添加更多注解。使用 @AnyRes能夠指示注解的參數(shù)可為任意類型的 R 資源。

盡管您可以使用 @ColorRes 指定某個(gè)參數(shù)應(yīng)為顏色資源,但是顏色整型(RRGGBBAARRGGBB 格式)無法識(shí)別為顏色資源。請(qǐng)改用 @ColorInt 注解指示某個(gè)參數(shù)必須為顏色整型。構(gòu)建工具會(huì)標(biāo)記不正確代碼,該代碼會(huì)將顏色資源 ID(例如 android.R.color.black)而不是顏色整型傳遞到已注解方法。

線程注解

線程注解可以檢查某個(gè)方法是否從特定類型的線程調(diào)用。支持以下線程注解:

:構(gòu)建工具會(huì)將 @MainThread@UiThread 注解視為可互換,因此,您可以從 @MainThread 方法調(diào)用 @UiThread 方法,反之亦然。不過,如果系統(tǒng)應(yīng)用在不同線程上帶有多個(gè)視圖,UI 線程可與主線程不同。因此,您應(yīng)使用 @UiThread 標(biāo)注與應(yīng)用的視圖層次結(jié)構(gòu)關(guān)聯(lián)的方法,使用 @MainThread 僅標(biāo)注與應(yīng)用生命周期關(guān)聯(lián)的方法。

如果某個(gè)類中的所有方法具有相同的線程要求,您可以向該類添加一個(gè)線程注解,以驗(yàn)證該類中的所有方法是否均從相同類型的線程調(diào)用。

線程注解的一個(gè)常見用途是驗(yàn)證 AsyncTask 類中的方法替換,因?yàn)榇祟悤?huì)執(zhí)行后臺(tái)操作并將結(jié)果僅發(fā)布到 UI 線程上。

筆記:一般情況下,安卓系統(tǒng)內(nèi)的一般應(yīng)用中,MainThread和UiThread是一樣的。@MainThread和@UiThread的注解可以相互替換。在特殊情況下,如個(gè)別系統(tǒng)應(yīng)用可能存在MainThread和UiThread不一致的場(chǎng)景,參考https://stackoverflow.com/questions/40784584/difference-between-the-main-thread-and-ui-thread

值約束注解

使用 @IntRange@FloatRange@Size 注解可以驗(yàn)證傳遞的參數(shù)的值。在應(yīng)用到用戶可能弄錯(cuò)其范圍的參數(shù)時(shí),@IntRange@FloatRange 都非常有用。

@IntRange 注解可以驗(yàn)證整型或長(zhǎng)整型參數(shù)值是否位于指定范圍內(nèi)。下面的示例可以確保 alpha 參數(shù)包含 0 至 255 范圍內(nèi)的整數(shù)值:

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

@FloatRange 注解可以檢查浮點(diǎn)或雙整型參數(shù)值是否位于指定的浮點(diǎn)值范圍內(nèi)。下面的示例可以確保 alpha 參數(shù)包含 0.0 至 1.0 的浮點(diǎn)值:

public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}

@Size 注解可以檢查集合或數(shù)組的大小,以及字符串的長(zhǎng)度。@Size 注解可用于驗(yàn)證以下質(zhì)量:

  • 最小大?。ɡ?@Size(min=2))
  • 最大大小(例如 @Size(max=2))
  • 確切大?。ɡ?@Size(2))
  • 表示大小必須為此倍數(shù)的數(shù)字(例如 @Size(multiple=2))

例如,@Size(min=1) 可以檢查某個(gè)集合是否不為空,@Size(3) 可以驗(yàn)證某個(gè)數(shù)組是否剛好包含三個(gè)值。下面的示例可以確保 location 數(shù)組至少包含一個(gè)元素:

int[] location = new int[3];
button.getLocationOnScreen(@Size(min=1) location);

權(quán)限注解

使用 @RequiresPermission 注解可以驗(yàn)證方法調(diào)用方的權(quán)限。要檢查有效權(quán)限列表中是否存在某個(gè)權(quán)限,請(qǐng)使用 anyOf 屬性。要檢查是否存在一組權(quán)限,請(qǐng)使用 allOf 屬性。下面的示例會(huì)標(biāo)注 setWallpaper() 方法,以確保方法的調(diào)用方擁有 permission.SET_WALLPAPERS 權(quán)限:

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;

此示例要求 copyFile() 方法的調(diào)用方同時(shí)具有外部存儲(chǔ)空間的讀寫權(quán)限:

@RequiresPermission(allOf = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE})
public static final void copyFile(String dest, String source) {
    ...
}

對(duì)于 intent 權(quán)限,請(qǐng)將權(quán)限要求添加到定義 intent 操作名稱的字符串字段上:

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

對(duì)于您需要單獨(dú)讀寫權(quán)限的內(nèi)容提供程序的權(quán)限,請(qǐng)?jiān)?@RequiresPermission.Read @RequiresPermission.Write注解中包含每個(gè)權(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)限

如果權(quán)限依賴于提供給方法參數(shù)的特定值,請(qǐng)對(duì)參數(shù)本身使用 @RequiresPermission,而不用列出具體權(quán)限。例如,startActivity(Intent) 方法會(huì)對(duì)傳遞到方法的 intent 使用間接權(quán)限:

public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle) {...}

在您使用間接權(quán)限時(shí),構(gòu)建工具將執(zhí)行數(shù)據(jù)流分析以檢查傳遞到方法的參數(shù)是否具有任何 @RequiresPermission 注解。隨后,它們會(huì)對(duì)方法本身強(qiáng)制參數(shù)的任何現(xiàn)有注解。在 startActivity(Intent) 示例中,當(dāng)一個(gè)不具有相應(yīng)權(quán)限的 intent 傳遞到方法時(shí),Intent類中的注解會(huì)針對(duì) startActivity(Intent) 的無效使用生成警告,如圖 1 中所示。

圖 1. startActivity(Intent) 方法上從間接權(quán)限注解生成的警告。

構(gòu)建工具會(huì)在 startActivity(Intent) 上從 [Intent](https://developer.android.com/reference/android/content/Intent.html) 類中相應(yīng) intent 操作名稱的注解生成警告:

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@RequiresPermission(Manifest.permission.CALL_PHONE)
public static final String ACTION_CALL = "android.intent.action.CALL";

如果需要,在標(biāo)注方法的參數(shù)時(shí),您可以將 @RequiresPermission 替換為 @RequiresPermission.Read 和/或 @RequiresPermission.Write。不過,間接權(quán)限 @RequiresPermission 不應(yīng)與讀取或?qū)懭霗?quán)限注解搭配使用。

返回值注解

使用 @CheckResult 注解可以驗(yàn)證實(shí)際使用的是方法的結(jié)果還是返回值。添加注釋來闡明可能令人困惑的方法的結(jié)果,而不是使用 @CheckResult 標(biāo)注每個(gè)非空方法。例如,新 Java 開發(fā)者經(jīng)常誤認(rèn)為 <*String*>.trim() 會(huì)移除原始字符串中的空格。使用 @CheckResult 標(biāo)注方法會(huì)在調(diào)用方未對(duì)方法返回值作任何處理的地方標(biāo)記 <*String*>.trim() 的使用。

下面的示例會(huì)標(biāo)注 checkPermissions()方法,以確保實(shí)際引用方法的返回值。它還會(huì)將 enforcePermission() 方法指定為要向開發(fā)者建議的替換方法:

@CheckResult(suggest="#enforcePermission(String,int,int,String)")
public abstract int checkPermission(@NonNull String permission, int pid, int uid);

CallSuper 注解

使用 @CallSuper注解可以驗(yàn)證替換方法是否會(huì)調(diào)用方法的超類實(shí)現(xiàn)。下面的示例會(huì)標(biāo)注 onCreate() 方法,以確保任何替換方法實(shí)現(xiàn)都會(huì)調(diào)用 super.onCreate()

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}

Typedef 注解

使用 @IntDef@StringDef注解,以便能夠創(chuàng)建整型和字符串集的枚舉注解來驗(yàn)證其他類型的代碼引用。Typedef 注解可以確保特定參數(shù)、返回值或字段引用特定的常量集。它們還可以完成代碼以自動(dòng)提供允許的常量。

Typedef 注解使用 @interface 聲明新的枚舉注解類型。@IntDef@StringDef 注解以及 @Retention 可以標(biāo)注新注解,并且為定義枚舉的類型所必需。@Retention(RetentionPolicy.SOURCE) 注解可以告知編譯器不將枚舉的注解數(shù)據(jù)存儲(chǔ)在 .class 文件中。

下面的示例說明了創(chuàng)建注解的具體步驟,此注解可以確保作為方法參數(shù)傳遞的值引用一個(gè)定義的常量:

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);

在您構(gòu)建此代碼時(shí),如果 mode 參數(shù)不引用一個(gè)定義的常量(NAVIGATION_MODE_STANDARD、NAVIGATION_MODE_LISTNAVIGATION_MODE_TABS),則會(huì)生成警告。

筆記:IntDef還可以直接在注解中定義常量。寫法參考http://blog.shamanland.com/2016/02/int-string-enum.html

您還可以組合 @IntDef@IntRange,以指示整型可以是給定的常量集或某個(gè)范圍內(nèi)的值。

允許將常量與標(biāo)志相結(jié)合

如果用戶可以將允許的常量與標(biāo)志(例如,|、& 和 ^,等等)相結(jié)合,則您可以通過 flag 屬性定義一個(gè)注解,以檢查某個(gè)參數(shù)或返回值是否會(huì)引用有效模式。下面的示例將使用一組有效的 DISPLAY_ 常量創(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 {}

...

在您使用注解標(biāo)志構(gòu)建代碼時(shí),如果經(jīng)過修飾的參數(shù)或返回值不引用有效模式,則將生成警告。

代碼可訪問性注解

使用 @VisibleForTesting@Keep 注解可以表示方法、類或字段的可訪問性。

@VisibleForTesting 注解指示一個(gè)代碼塊的可見性是否高于讓代碼變得可測(cè)試所需要的水平。

@Keep 注解可以確保如果在構(gòu)建時(shí)縮減代碼,標(biāo)注的元素不會(huì)移除。它一般會(huì)添加到通過反射訪問的方法和類中,以阻止編譯器將代碼視為未使用。

筆記:多用注解來提高代碼的規(guī)范性和可讀性。

參考文檔:
https://stackoverflow.com/questions/40784584/difference-between-the-main-thread-and-ui-thread

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容