Support Annotation Library是從Android Support Library 19.1 開始引入的一個全新的函數包,它包含一系列有用的元註解,用來幫助開發者在編譯期間發現可能存在的Bug。Support Library本身也使用Annotation Library 提供的註解來完善自身的代碼質量,Android Studio 提供可視化的交互以便開發者發現問題。
Android Support Library 發展到現在已經不止是一個jar包了,而是拆分成多個獨立的Jar包,例如support-v4、support-v7、gridlayout-v7、design、cardview-v7等等。而Annotation Libary 也是其中之一,默認情況下是不會包含在工程中的,如果我們的SDK已經安裝了android Support Repository,那麼我們打開Project Structure 對話框,並選中一個Module,選中Dependencies選項,點擊“+”按鈕,在彈出的Choose Library Dependency 對話框中輕鬆找到Annotation Library。
點擊添加後,在Module的build.gradle文件中會新增加Annotation函數庫的依賴如下:
compile 'com.android.support:support-annotations:26.0.0-alpha1'
下面我們按照類型進行一一介紹。
1. Nullness 註解
此類型包含如下內容:
- @Nullable作用於函數參數或返回值,標記參數或返回值可以爲空。
- @NonNull作用於函數參數或返回值,標記參數或返回值不能爲爲空。
如果在函數參數或返回值使用了上述註解,而又出現違反該註解的代碼時,Android Studio 會給出提示,同時使用Android Lint進行靜態代碼掃描,也會顯示出錯提示。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test(null);
}
private void test(@NonNull String str) {
Log.e(TAG,str);
}
我們用Android Lint掃描這個文件的話,在掃描結果中會出現如下錯誤:
2. 資源類型註解
我們知道,資源是以int整型表示,並保存在R.Java文件中。這就意味着在一個需要Layout資源值函數傳入String字符串,在編譯時不會報錯,只有在運行時纔會報錯,爲了防止這種情況的出現,可以使用資源類型註解。
資源類型的註解作用於函數參數、返回值及類的變量,每種資源類型對應一種註解。
AnimatorRes:標記整型值是android.R.animation類型。
AnimRes:標記整型是android.R.anim類型。
AnyRes:標記整型是任何一種資源類型,如果確切知道表示的是哪一個具體資源的話,建議顯式指定。
ArrayRes:標記整型是android.R.array類型。
AttrRes:標記整型是android.R.attr類型。
BoolRes:標記整型是布爾類型。
ColorRes:標記整型是android.R.color類型。
DrawableRes:標記整型是android.R.drawable類型。
FranctionRes:標記整型值是fraction類型,這個比較少見,這種類型資源常見於Animation
Xml中,比如50%,表示佔parent的50%IdRes:標記整型是android.R.id類型。
IntegerRes:標記整型是android.R.integer類型。
InterpolatorRes:標記整型是android.R.interpolator類型,插值器,在Animation
Xml中使用較多。LayoutRes:標記整型是android.R.layout類型。
MenuRes:標記整型是android.R.menu類型。
RawRes:標記整型是android.R.raw類型。
StringRes:標記整型是android.R.string類型。
StyleableRes:標記整型是android.R.styleable類型。
StyleRes:標記整型是android.R.style類型。
XmlRes:標記整型是android.R.xml類型。
看一下例子。這裏傳入字符串若是不加@LayoutRes,是不會報錯,
若是加了就會報錯。提前知道錯誤在哪。這樣就會少很多麻煩。舉個例子,在AppCompatAcitivity的setContentView函數使用@LayoutRes標記它的參數。
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
3. 類型定義註解
在Android開發中,整型值不止經常用來代表資源引用值,而且經常用來代替枚舉值。@IntDef註解用來創建一個整型類型定義的新註解,我們可以使用這個新註解來標記自己編譯的API。先看看@IntDef的源碼。
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
long[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}
這裏面可以定義一個布爾值,還可以定義多個long類型的值。那麼就以long數組舉例。
public abstract class AnnotationTest {
public static final int TEST_1 = 0;
public static final int TEST_2 = 1;
public static final int TEST_3 = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({TEST_1,TEST_2,TEST_3})
public @interface TestAnnotation{}
@TestAnnotation
public abstract int getTestAnnotation();
public abstract void setTestAnnotation(@TestAnnotation int testAnnotation);
}
這裏給TestAnnotation註解加上了@IntDef,這樣在使用TestAnnotation的時候必須傳入指定參數,若是非法在編譯時就會報異常。
使用就非常簡單了,找個類繼承抽象類,實現方法。在調用的時候,只能傳入指定的TEST_1,TEST_2,TEST_3。
這樣就可以自定義資源類型註解,非常方便。
4. 線程註解
Android應用開發過程中,經常會涉及到多種線程的使用,界面相關操作必須在主線程,而耗時操作如文件下載等必須在後臺線程中,
線程註解相關有四種。
@UiThread:標記運行在UI線程,一個應用只有一個UI線程,多數是用於View的標註。(這裏先前有錯誤,感謝樓下評論朋友提醒)
@MainThread:標記運行在主線程,一個應用只有一個主線程,主線程也是@UiThread線程。通常情況下,我們使用@MainThread
來註解生命週期相關函數,使用@UiThread來註解視圖相關函數,一般情況下@MianThread和@UiThraed是可以互換的。
@WorkerThread:標記運行在後臺運行線程。
@BinderThread:標記運行在Binder線程。
一個典型的例子就是AsyncTask的源碼,我們截取部分代碼如下。
@MainThread
protected void onPreExecute() {
}
@MainThread
protected void onPostExecute(Result result) {
}
@MainThread
protected void onProgressUpdate(Progress... values) {
}
5. RGB顏色值註解
在資源類型註解中我們使用@ColorRes來標記參數類型需要傳入顏色類型的id,而使用@ColorInt註解是標記參數類型需要傳入RGB或者ARGB顏色值的整型值,在TextView的源碼中可以找到@ColorInt的例子。
@android.view.RemotableViewMethod
public void setTextColor(@ColorInt int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
6. 值範圍註解
當函數參數的取值在一定範圍時,可以使用註解來防止調用者傳入錯誤的參數,主要註解有三種註解。
@Size:對於類似數組、集合和字符串之類的參數,我們可以使用@Size註解來表示這些參數的大小。用法:
@Size(min=1)//可以表示集合不可以爲空
@Size(max=23)//可以表示字符串最大字符個數爲23
@Size(2)//表示數組元素個數爲2個
@Size(multiple=2)//可以表示數組大小是2的倍數
@IntRange:參數類型是int或者long,用法如下
public void setAlpha(@IntRange(from=0,to=255) int alpha){...}
- @FloatRange:參數類型是float或者double,用法如下。
public void setAlpha(@FloatRange(from=0.0,to=1.0) float alpha){...}
7. 權限註解
Android應用在使用某些系統功能時,需要在AndroidManifest,xml中聲明權限,否則在運行時就會提示缺失對應的權限,爲了在編譯時及時發現權限的缺失,我們可以使用@RequiresPermission註解。
- 如果函數調用需要聲明一個權限,語句如下:
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
- 如果需要一個集合至少一個權限,語句如下:
@RequiresPermission(anyOf= {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String Provider);
- 如果同時需要多個權限,語句如下。
@RequiresPermission(allOf = {
Manifest.permission.READ_HISTORY_BOOKMARKS,
Manifest.permission.WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr,String url,boolean real);
- 對於Intent調用所需權限,可以在Intent的ACTION字符串定義處添加註解。語句如下:
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERRAVLE = "android.bluetooth.adapter.REQUEST_DISCOVERRAVLE";
- 對於ContentProvider所需權限,可能有讀和寫兩個操作。對應不同的權限。
@RequiresPermission.Read(@RequestPermission(READ_HISTORY_BOOLMARKS))
@RequiresPermission.Write(@RequestPermission(WRITE_HISTORY_BOOLMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
8. 重寫函數註解
如果API允許重寫某個函數,但是要求在重寫該函數時需要調用super父類的函數,否則代碼邏輯可能會出錯,那麼可以加註解@CallSuper來提示開發者。
@CallSuper
protected void onCreate(@Nullable Bundle saveInstanceState);
9. 返回值註解
如果我們編寫的函數需要調用對返回值做某種處理,那麼可以使用@CheckResult註解來提示開發者。當然我們沒有必要對每個非空返回值的函數都添加這個註解,該註解的主要目的是讓調用者在使用API時不至於懷疑該函數是否會產生副作用。看下Context類中checkPermission中的源碼:
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
@CheckResult(suggest="#enforceCallingPermission(String,String)")
@PackageManager.PermissionResult
public abstract int checkCallingPermission(@NonNull String permission);
如果調用者沒有檢查這兩個函數的返回值,那麼就會警告,警告信息中包含suggest屬性中的內容。
10. @VisibleForTesting註解
單元測試中可能需要訪問到一些不可見的類、函數或者變量,這時可以使用@VisibleForTesting註解來使其對測試可見。
11. @Keep註解
@keep是用來標記在Proguard混淆過程中不需要混淆的類或者方法。在混淆時一些不需要混淆的會使用
-keep class com.foo.bar{public static method>}
有了@Keep之後,就可以在編碼時標註出一些不需要混淆的類或者方法
public class AnnotationDemo {
@Keep
public void doSomething(){
// ...
}
// ...
}
12. @SuppressWarnings忽略警告
java.lang.SuppressWarnings是J2SE 5.0中標準的Annotation之一。可以標註在類、字段、方法、參數、構造方法,以及局部變量上。作用:告訴編譯器忽略指定的警告,不用在編譯完成後出現警告信息。
注意:SuppressWarnings是java.lang包下的,之前說的都是android.support.annotation包下的
使用:
- @SuppressWarnings(“”)
- @SuppressWarnings({})
- @SuppressWarnings(value={})
示例:
@SuppressWarnings(“unchecked”)
告訴編譯器忽略 unchecked 警告信息,如使用List,ArrayList等未進行參數化產生的警告信息。
@SuppressWarnings(“serial”)
如果編譯器出現這樣的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long
使用這個註釋將警告信息去掉。
@SuppressWarnings(“deprecation”)
如果使用了使用@Deprecated註釋的方法,編譯器將出現警告信息。
使用這個註釋將警告信息去掉。@SuppressWarnings(“unchecked”, “deprecation”)
告訴編譯器同時忽略unchecked和deprecation的警告信息。
@SuppressWarnings(value={“unchecked”, “deprecation”})
等同於@SuppressWarnings(“unchecked”, “deprecation”)
1、抑制單類型警告
@SuppressWarnings("unchecked")
public void addItems(String item){
@SuppressWarnings("rawtypes")
List items = new ArrayList();
items.add(item);
}
2、抑制多類型警告
@SuppressWarnings(value={"unchecked", "rawtypes"})
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
3、抑制全部警告
@SuppressWarnings("all")
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
註解目標
通過 @SuppressWarnings 的源碼可知,其註解目標爲類、字段、函數、函數入參、構造函數和函數的局部變量。而家建議註解應聲明在最接近警告發生的位置。
到這裏基本註解都已經說完了,最後說明一下,如果函數庫中使用Annotation Library,並使用Gradle生成aar壓縮包,那麼在編譯時Android Gradle插件會取出這些註解信息並打包在aar文件中,以便函數庫的調用者正常使用我們的註解信息。aar文件中的annotations.zip文件就是抽取出來的註解信息。
參考:
@SuppressWarnings忽略警告
《Android高級進階》