概述
Java語言提供了Annotation的機制,讓描述性的元數據能夠和代碼共存。通常我們可以利用Annotation,來做一些標志性的說明。然而Annotation必須和相應的解析工具一起才能工作。合理的運用Annotation,會帶來一些額外的效果。
本文不討論Annotation的基礎語法以及基礎使用方法。
Android用Annotation干了什么?
Java這種強類型的語言,在編寫代碼的時候,編輯器就會通過語法檢查,來阻止一些錯誤的發生。相比於一些弱類型的語言,更安全。Annotation作為元數據而存在,結合上運行在編輯期的解析工具,可以做到更細致的代碼檢查,可以說是一種對語法檢查的擴充吧。
Android就充分利用了這一點,Android在提供了大量的Annotation的同時,也在ADT中提供了操作這些Annotation的工具,主要體現在代碼檢查階段。下面我們會詳細看一下Android提供的Annotation。這些Annotation不僅可以讓代碼在編寫的時候,少出一些問題,而且可以對一個類、方法等做詳細的說明。這些Annotation也可以指導使用API的開發人員,正確的使用API。
Android是如何運用Annotation來進行代碼檢查的?
Android提供了幾大類的Annotation,用來配合其相應的解析工具,來對代碼做更嚴格的檢查。
1、強制方法調用類型。
作用:指示所有Override該方法的方法,都需要調用調用該方法。
Annotations:@CallSuper
使用場景:你設計了一個框架,里面有一些基礎方法,各子類需要覆寫該方法,但是一定要調用父類的方法。這時候可以使用這個Annotation來強制子類調用父類的方法。
示例代碼:
//父類 @CallSuper protected void fun() { System.out.println("parent fun()"); } //子類 @Override protected void fun() { super.fun(); }
如果不調用 super.fun(),則會出現提示:Overriding method should call super.fun。
2、指定固定常量型。
作用:指示一個邏輯類型,並且這個邏輯類型的值只能取固定的常量。
Annotations:@IntDef
@StringDef
使用場景:作為一個特定類型的有限個取值,可以使用Enum或者使用常量,但是Enum可以有類型檢查,而常量沒有。眾所周知,因為內存占用的問題,Android不推薦使用Enum,那就只能使用常量,但是常量不能做類型檢查,所以Android就提供了這樣一種方法,來解決此問題,讓指定的屬性值只能取定義好的常量。
示例代碼:
//定義常量 public static final int INT_1 = 1; public static final int INT_2 = 2; //指定該Annotation描述的對象,只能使用這兩個常量 @IntDef({INT_1, INT_2}) public @interface MyType { } //如果一個方法只想接收這兩個常量(常量名稱,而不是常量的值)作為參數,那么可以這樣使用 private void f (@MyType int time) { }
如果調用f()時,不傳遞這兩個常量,比如f(2)。那么就會報錯:Must be one of: INT_1, INT_2。
3、資源對象的值類型指定型。
作用:指示所描述的參數、字段、返回值必須是指定的資源類型的引用。
Annotations:@AnimatorRes
@AnimRes
@AnyRes
@ArrayRes
@AttrRes
@BoolRes
@ColorRes
@DimenRes
@DrawableRes
@FractionRes
@IdRes
@IntegerRes
@InterpolatorRes
@LayoutRes
@MenuRes
@PluralsRes
@RawRes
@StringRes
@StyleableRes
@StyleRes
@TransitionRes
@XmlRes
使用場景:如果你有一個方法,需要一個 R.id 的值作為參數,或者一個方法,希望返回值是 R.anim 類型的引用,那么這個時候就可以使用這些Annotation。
示例代碼:
//希望接收一個id作為參數 private void f (@IdRes int id) { } private void call() { //但是實際上不小心傳遞了一個layout進去。 f(R.layout.day_view); }
那么這個時候就會報錯:Expected resource of type id。
4、取值范圍型。
作用:指示一個int、float或者double類型的值,合理的取值范圍。
Annotations:@FloatRange
@IntRange
使用場景:比如有一個方法,接收一個int型的透明度參數,那么該方法就會希望傳進來的值應該是0~255,這時就可以使用該Annotation。
示例代碼:
//希望接收一個[0, 255]的值作為透明度 private void setAlpha (@IntRange(from = 0, to = 255) int alpha) { } private void call() { //但是實際上不小心傳遞了一個300進去。 setAlpha(300); }
那么這個時候就會報錯:Value must be ≥ 0 and ≤ 255 (was 300)。
5、線程標識型。
作用:指示某個對象、構造器或者方法,應該在指定的類型的線程運行。
Annotations:@BinderThread
@MainThread
@UiThread
@WorkerThread
使用場景:如果有一個方法,比較耗時,所以它應該放在工作線程執行。這時候可以用@WorkerThread來標注該方法,當在主線程調用該方法的時候,就會報錯。
示例代碼:
//該方法比較耗時,所以放在子線程執行 @WorkerThread private void shouldRunOnWorkerThread() { } @MainThread private void runOnMainThread() { //在主線程調用該方法,會報錯。 shouldRunOnWorkerThread(); }
那么這個時候就會報錯:Method shouldRunOnWorkerThread must be called from the worker thread, currently inferred thread is main。
像 Activity 的onCreate方法,都是被標注為主線程執行的。
@MainThread @CallSuper protected void onCreate(@Nullable Bundle savedInstanceState) { .......................; }
6、空指針指示型。
作用:指示某個參數、返回值等是否可以為空。
Annotations:@NonNull
@Nullable
使用場景:比如一個方法,可能返回一個null,可以使用@Nullable來標注,那么使用該方法的地方,一般都要做null判斷。
7、混淆指示類型。
作用:指示被描述的對象,在編譯的時候不要被混淆。
Annotations:@Keep
使用場景:當一個方法、屬性、對象等,需要被反射使用時,比如通過反射來實現工廠。這時候可以使用該Annotation來標記,這樣在編譯的時候,就不會被混淆(如果開了混淆的話)。
示例代碼:
//抽象產品,避免被混淆 @Keep class Product {} //產品A,避免被混淆 @Keep class ProductA extends Product{} //產品B,避免被混淆 @Keep class ProductB extends Product{} private Product getProduct(String productName) { //使用反射創建具體的Product return ...; }
8、參數長度限定類型。
作用:指示被描述的對象應該有明確的大小。
Annotations:@Size
使用場景:當一個參數,需要最小的大小時,使用該Annotation,比如,一個數組最少有2個元素、一個String最低長度為3等。
示例代碼:
//要求param的長度最小為2 private void minSize(@Size(min = 2) String param) { } private void callMinSize() { //傳遞長度只有1的"s"時,會報錯 minSize("s"); }
那么這個時候就會報錯:Length must be at least 2 (was 1)。
9、其他類型。
作用:指示一個int值是顏色類型的值(AARRGGBB)。
Annotations:@ColorInt
示例代碼:
//標明該常量代表顏色值 @ColorInt public static final int BLACK = 0xFF000000; //標明該方法希望接收一個代表顏色值的參數 public setColor(@ColorInt int color) { }