Android 中註解的使用

Airsaid發表於2017-09-18

轉載請標明出處: juejin.im/post/59bf5e…
本文出自:周遊的主頁

前言

Android Support Library 從 19.1 版本開始引入了一個新的註解庫,其中包含了很多的元註解,使用它們修飾我們的程式碼, 可以讓我們提高程式的開發效率,讓我們更早的發現問題。以及對程式碼施以規範,讓程式碼更加有可讀性。這篇文章就來簡單瞭解下這些註解,以及其使用。如有錯誤和遺漏,歡迎留言或補充~

注:現在我們新建專案直接就依賴了 support.appcompat 包,其中已經依賴了 annotations 包。如果你的專案中寫如下註解報錯,可以新增註解包:

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}複製程式碼

@IntDef & @StringDef

替代 Java 中列舉的註解,以 @IntDef 為例,定義和使用如下:

@IntDef({RED, BLUE, YELLOW})
@Retention(RetentionPolicy.SOURCE)
public @interface LightColors{};

public static final int RED = 1;
public static final int BLUE = 2;
public static final int YELLOW = 3;

public void setColor(@LightColors int color){
}複製程式碼
  • @interface:宣告新的列舉註解型別。
  • @Retention(RetentionPolicy.SOURCE):告知編譯器不將列舉的註解資料儲存在 .class 檔案中。

如果允許常量與標誌(例如:|、& 和 ^ 等等)相結合,則我們可以使用 flag 屬性,如:

@IntDef(flag = true, value = {RED, BLUE, YELLOW})複製程式碼

使用:

setColor(RED | BLUE);複製程式碼

@Nullable & @NonNull

  • @Nullable:註解的元素可以為 null。
  • @NonNull:註解的元素不可以為 null。

上面的註解可以修飾如下元素:
1,方法引數。如:

@Nullable
private String data;複製程式碼

2,方法的返回值。 如:

@Nullable
public String getData(){
    return data;
}複製程式碼

3,成員屬性。如:

public void setData(@Nullable String data){
}複製程式碼

當用空的引數傳給被 @NonNull 修飾的方法引數的方法時,會給出如下警告提示(編譯不會報錯):

passing "null" argument to parameter annotated as @NotNull複製程式碼

@FloatRange & @IntRange

@FloatRange 和 @IntRange 是用於限定範圍的註解。其中 @FloatRange 是限定 float 型別的,而 @IntRange 是限定 int 型別的。它們同上註解一樣,可以修飾方法引數、方法返回值、成員屬性。

以 @IntRange 為例,修飾方法引數的定義如下:

public void setAge(@IntRange(from = 1, to = 180) int age){
}複製程式碼

如果呼叫該方法傳的引數不在 1 - 180 的範圍內, 如:setAge(0),那麼編譯會直接報如下錯:

value must be ≥ 1 and ≤ 180 (was 0)複製程式碼

@Size

@Size 註解的作用是限定長度的,同上註解一樣,可以修飾方法引數、方法返回值、成員屬性。

  • 限定字串的長度:
    public void setData(@Size(4) String data){
    }複製程式碼
    當傳入的字串長度不等於 4 時,編譯器會直接報錯:
    Length must be exactly 4複製程式碼
  • 限定陣列的長度:
    public void setData(@Size(4) int[] data){
    }複製程式碼
  • 特殊的限定,如限定為 2 的倍數:
    public void setData(@Size(multiple = 2) int[] data){
    }複製程式碼
    限定最小的長度:
    @Size(min = 2)複製程式碼
    限定最大的長度:
    @Size(max = 2)複製程式碼
    等同於 @Size(2) 寫法:
    @Size(value = 2)複製程式碼

@RequiresPermission

該註解作用是表明方法所執行的內容需要許可權。如需要單個許可權:

@RequiresPermission(Manifest.permission.CALL_PHONE)
private void callPhone(String phone){
}複製程式碼

需要一組許可權:

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

對於 intent 許可權,我們可以定義在 intent 操作名稱的字串上:


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

對於需要單獨讀寫許可權的內容提供程式的許可權,我們可以在 @RequiresPermission.Read 或 @RequiresPermission.Write 註解中包含每個許可權要求:

@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) 方法:

public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle) {...}複製程式碼

當我們使用這種方式(間接許可權)時,構建工具將執行資料流分析以檢查傳遞到方法的引數是否具有任何 @RequiresPermission 註解。如:

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:1234567890"));
startActivity(intent);複製程式碼

這裡的 startActivity(intent) 就直接報錯了:

call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`複製程式碼

因為 Intent.ACTION_CALL 中標記了許可權註解:

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

@CheckResult

@CheckResult 註解是作用於方法上的,作用是檢驗有沒有處理返回值。如果沒有處理返回值則會報錯。

@CheckResult
public String getData(String data) {
    return data.trim();
}複製程式碼

執行緒註解

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

  • @MainThread:表示標記的方法只應在主執行緒呼叫。如果標記的是一個類,那麼該類中的所有方法都應該是在主執行緒被呼叫。例:(通常,應用程式的主執行緒也是 Ui 執行緒。但是,在特殊情況下,應用程式的主執行緒可能不是其 Ui 執行緒)

    @MainThread
    public void deliverResult(D data) { ... }複製程式碼
  • @UiThread:表示標記的方法或建構函式只應該在 Ui 執行緒上呼叫。如果標記的是一個類,那麼該類中的所有方法都應是在 Ui 執行緒被呼叫。例:

    @UiThread
    public abstract void setText(@NonNull String text) {...}複製程式碼
  • @WorkerThread:表示標記的方法只應該在工作執行緒上呼叫。如果標記的是一個類,那麼該類中的所有方法都應是在一個工作執行緒上呼叫。例:

    @WorkerThread
    protected abstract FilterResults performFiltering(CharSequence constraint);複製程式碼
  • @BinderThread:表示標記的方法只應在繫結執行緒上呼叫。如果標記的是一個類,那麼該類中的所有方法都應是在繫結執行緒被呼叫。例:

    @BinderThread
    public BeamShareData createBeamShareData() { ... }複製程式碼
  • @AnyThread:表示可以從任何執行緒呼叫帶標記的方法。如果標記的是一個類,那麼該類中的所有方法都可以從任何執行緒中呼叫。例:

    @AnyThread
    public void deliverResult(D data) { ... }複製程式碼

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

資源註解

在 Android 中幾乎所有的資源都有其對於的 id,我們在使用的時候可以直接通過 id 來,如:

textView.setText(getResources().getText(R.string.app_name));複製程式碼

但是這樣如果沒有寫指定的資源註解的話就會風險,比如隨便傳了個 0,那麼就會找不到對應的資源。
為了避免由於自己的粗心大意而引發的錯誤,我們就可以使用資源註解了,如:

public int getText(@StringRes int id){
}複製程式碼

這樣當我們呼叫該方法時,如果傳遞的引數並不是 String 型別的資源 id,那麼編譯器就會報錯提示。

除了 @StringRes 資源註解外,還有:

  • @IntegerRes:R.integer 型別資源。
  • @AnimatorRes:R.animator 型別資源。
  • @AnimRes:R.anim 型別資源。
  • @ArrayRes:R.array 型別資源。
  • @AttrRes:R.attr 型別資源。
  • @BoolRes:R.bool 型別資源。
  • @ColorRes:R.color 型別資源。
  • @DimenRes:R.dimen 型別資源。
  • @DrawableRes:R.drawable 型別資源。
  • @FractionRes:R.fraction 型別資源。(百分比)
  • @IdRes:R.id 型別資源。
  • @InterpolatorRes:R.interpolator 型別資源。(插值器)
  • @LayoutRes:R.layout 型別資源。
  • @MenuRes:R.menu 型別資源。
  • @PluralsRes:R.plurals 型別資源。(複數)
  • @RawRes:R.raw 型別資源。
  • @StyleableRes:R.styleable 型別資源。
  • @StyleRes:R.style 型別資源。
  • @TransitionRes: R.transition 型別資源。
  • @XmlRes:R.xml 型別資源。
  • @AnyRes:未知資源。(表示自己不知道是什麼型別的資源。比如有可能為 R.drawable 也有可能是 R.string。)

@ColorInt

@ColorInt 註解的作用為:限定顏色值。(ARGB:0xAARRGGBB)

public void setColor(@ColorInt int color) {

}複製程式碼

如果直接使用資源 id,則會報錯,如下:

setColor(R.color.colorAccent)// 報錯複製程式碼

正確的使用是:

setColor(0xFFFF00FF);複製程式碼

如果要使用資源 id,則可以通過 ContextCompat.getColor() 方法來:

setColor(ContextCompat.getColor(context, R.color.colorAccent));複製程式碼

@CallSuper

該註解用於修飾方法,表示重寫該方法時必須呼叫 super 方法。如 onCreate() 方法:

@CallSuper
protected void onCreate(Bundle savedInstanceState) {
}複製程式碼

重寫 onCreate() 方法時,必須呼叫 super 方法:

super.onCreate(savedInstanceState);複製程式碼

否則報錯。

@VisibleForTesting & @Keep

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

  • @VisibleForTesting:該註解只起到一個註釋的作用,告訴其他開發者被標記的程式碼為什麼有這麼大的可見程度(為了測試方便)。因此,經常用來修飾 public 或 protected,用來修飾 private 並不會報錯,但是沒有意義。
  • @Keep:標記的指定程式碼在混淆時不會被混淆。

參考:

相關文章