最近在看一些開(kāi)源項(xiàng)目的源碼捕透,發(fā)現(xiàn)了Android中的一些很有意思的注解聪姿,于是歸納總結(jié)了一下,以后在自己的項(xiàng)目中也可以嘗試使用乙嘀。
首先末购,需要在gradle的dependencies中加入
compile 'com.android.support:support-annotations:25.2.0'
當(dāng)前的最新版本是25.2.0
Android注解有8種類(lèi)型,分別是Nullness注解虎谢、資源類(lèi)型注解盟榴、線(xiàn)程注解、變量限制注解婴噩、權(quán)限注解曹货、結(jié)果檢查注解、CallSuper注解讳推、枚舉注解顶籽。
Nullness注解
- @NonNull
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
- @Nullable
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface Nullable {
}
從名字上就可以很明顯的看出,@NonNull表示這個(gè)參數(shù)银觅、變量等不能為空礼饱,而@Nullable則表示可以為空,舉個(gè)例子:
private void test(@NonNull String test) {
}
如果我們有這樣的一個(gè)函數(shù)究驴,用@NonNull注解表示參數(shù)不能為空镊绪,如果我們這樣調(diào)用這個(gè)函數(shù)
test(null);
我們會(huì)得到這樣的警告提示,告訴我們這個(gè)參數(shù)被注解為@NonNull
如果我們將這個(gè)函數(shù)改為:
private void testNonNull(@Nullable String test) {
}
或者沒(méi)有任何注解時(shí)洒忧,就沒(méi)有提示了蝴韭。
資源類(lèi)型注解
所有以“Res”結(jié)尾的注解,都是資源類(lèi)型注解熙侍。大概包括:@AnimatorRes榄鉴、@AnimRes履磨、@AnyRes、@ArrayRes庆尘、@AttrRes剃诅、@BoolRes、@ColorRes驶忌、@DimenRes矛辕、@DrawableRes、@FractionRes付魔、@IdRes聊品、@IntRes、@IntegerRes几苍、@InterpolatorRes翻屈、@LayoutRes、@MenuRes擦剑、@PluralsRes妖胀、@RawRes芥颈、@StringRes惠勒、@StyleableRes、@StyleRes爬坑、@TransitionRes纠屋、@XmlRes
使用方法也都是類(lèi)似的,這里舉個(gè)例子:
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}
private String getStringById(@StringRes int stringId) {
return getResources().getString(stringId);
}
如果我們這樣寫(xiě)String name = getStringById(R.string.app_name);
是不會(huì)有問(wèn)題的盾计,但是團(tuán)隊(duì)中的其他小伙伴在調(diào)用的時(shí)候?qū)戝e(cuò)了怎么辦售担?比如String name = getStringById(R.layout.activity_main);
如果@StringRes注解,我們看不到任何的提醒署辉,而運(yùn)行時(shí)就會(huì)報(bào)錯(cuò)族铆,但是如果加上了@StringRes注解,我們就可以看到這樣的錯(cuò)誤提示:
線(xiàn)程注解
包括@AnyThread哭尝、@UiThread和@WorkerThread哥攘,表明需要運(yùn)行在什么線(xiàn)程上。
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface WorkerThread {
}
例如有一個(gè)函數(shù)材鹦,需要做一些耗時(shí)操作逝淹,我們希望它不要運(yùn)行在主線(xiàn)程上
@WorkerThread
private void testThread() {
// 耗時(shí)操作
}
如果我們?cè)谥骶€(xiàn)程中調(diào)用這個(gè)函數(shù)會(huì)怎么樣呢?
而如果這樣調(diào)用就不會(huì)有問(wèn)題桶唐,這樣就保證了我們這個(gè)耗時(shí)操作不會(huì)執(zhí)行在主線(xiàn)程中栅葡。
new Thread(new Runnable() {
public void run() {
testThread();
}
}).start();
變量限制注解
變量限制注解主要包含@IntRange和@FloatRange兩種,使用方法類(lèi)似尤泽,都是限制了范圍欣簇,這里以@IntRange為例规脸。
@Retention(CLASS)
@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
/** Smallest value, inclusive */
long from() default Long.MIN_VALUE;
/** Largest value, inclusive */
long to() default Long.MAX_VALUE;
}
源碼中可以看到,這里包含了from()
和to()
醉蚁,默認(rèn)值分別是Long的最小值Long.MIN_VALUE
和Long的最大值Long.MAX_VALUE
燃辖。
例如我們有個(gè)方法,需要限制輸入的范圍网棍,我可以這樣寫(xiě):
private void testRange(@IntRange(from = 1, to = 10) int number) {
}
如果調(diào)用者輸入了一個(gè)超出范圍的值時(shí)黔龟,會(huì)這樣提示他。
權(quán)限注解
如果我們有方法需要使用某種權(quán)限滥玷,可以加上@RequiresPermission這個(gè)注解氏身。
@RequiresPermission(Manifest.permission.CALL_PHONE)
private void testPermission() {
}
比如這里需要打電話(huà)的權(quán)限,但是我并沒(méi)有在應(yīng)用中加入該權(quán)限惑畴。
當(dāng)我們調(diào)用函數(shù)時(shí)蛋欣,就會(huì)有這樣的錯(cuò)誤提示。好吧如贷,那我把權(quán)限加上陷虎,發(fā)現(xiàn)還是有錯(cuò)誤提示。
沒(méi)有錯(cuò)杠袱,AS就是這么強(qiáng)大尚猿,會(huì)告訴我們這個(gè)權(quán)限可能會(huì)被用戶(hù)拒絕,所以我們應(yīng)該在代碼中對(duì)這個(gè)權(quán)限進(jìn)行檢查楣富。
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
testPermission();
這樣就沒(méi)有問(wèn)題了凿掂。
結(jié)果檢查注解
如果我們寫(xiě)了一個(gè)有返回值的函數(shù),并且我們希望調(diào)用者對(duì)這個(gè)返回值進(jìn)行使用或者檢查纹蝴。這個(gè)時(shí)候@CheckResult注解就派上用場(chǎng)了庄萎。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CheckResult {
/** Defines the name of the suggested method to use instead, if applicable (using
* the same signature format as javadoc.) If there is more than one possibility,
* list them all separated by commas.
* <p>
* For example, ProcessBuilder has a method named {@code redirectErrorStream()}
* which sounds like it might redirect the error stream. It does not. It's just
* a getter which returns whether the process builder will redirect the error stream,
* and to actually set it, you must call {@code redirectErrorStream(boolean)}.
* In that case, the method should be defined like this:
* <pre>
* @CheckResult(suggest="#redirectErrorStream(boolean)")
* public boolean redirectErrorStream() { ... }
* </pre>
*/
String suggest() default "";
}
比如有這樣一個(gè)方法,返回了String塘安。
@CheckResult
private boolean testCheckResult() {
return true;
}
如果我們不關(guān)心他的返回值糠涛。
提示我們結(jié)果沒(méi)有被使用。如果改為
boolean result = testCheckResult();
就不會(huì)有問(wèn)題了兼犯。@CheckResult注解保證了我們方法的返回值一定會(huì)得到使用忍捡。
CallSuper注解
如果我們有一個(gè)父類(lèi)Father,有一個(gè)方法display()免都,有一個(gè)子類(lèi)Child繼承了Father锉罐,并重寫(xiě)了display()方法,并不會(huì)有任何問(wèn)題绕娘。
class Father {
public void display() {
Log.i(TAG, "display: Father");
}
}
class Child extends Father {
@Override
public void display() {
Log.i(TAG, "display: Child");
}
}
但是脓规,如果我想讓子類(lèi)Child在調(diào)用display()方式也將父類(lèi)Father的某些信息一起打印出來(lái)怎么辦?沒(méi)錯(cuò)险领,在子類(lèi)的display()方法中調(diào)用super.display();
即可侨舆。那么秒紧,我們?cè)趺幢WC子類(lèi)就一定會(huì)調(diào)用super的方法呢?@CallSuper注解登場(chǎng)挨下。
@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CallSuper {
}
@CallSuper注解表示重寫(xiě)的方法必須調(diào)用父類(lèi)的方法熔恢,注意,這里的Target只有METHOD臭笆,并沒(méi)有CONSTRUCTOR叙淌,所以構(gòu)造函數(shù)是不能使用這個(gè)注解的。
還是剛才的例子愁铺,如果我們?cè)诟割?lèi)的方法上加上@CallSuper注解鹰霍,這個(gè)時(shí)候子類(lèi)中重寫(xiě)的方法就會(huì)提示這樣的錯(cuò)誤。
這樣就提醒我們需要加上super的方法茵乱。
枚舉注解
Android官方強(qiáng)烈建議不要在Android程序里面使用到enum茂洒,官方的Training課程里面有下面這樣一句話(huà):Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
既然官方都這樣說(shuō)了,那就盡量不要使用枚舉了瓶竭,可是不使用枚舉使用什么呢督勺?沒(méi)錯(cuò),靜態(tài)常量斤贰。
舉個(gè)例子智哀,比如我自己寫(xiě)了一個(gè)提示框,需要提示用戶(hù)一些信息腋舌,所以這樣寫(xiě):
public class MyTip {
public static void show(String message) {
// 顯示提示框
}
}
我希望這個(gè)提示框在顯示一定時(shí)間后自動(dòng)關(guān)掉盏触,所以定義了兩個(gè)靜態(tài)常量渗蟹,一個(gè)長(zhǎng)時(shí)間一個(gè)短時(shí)間块饺,并且作為show方法的一個(gè)參數(shù)。
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
public static void show(String message, int type) {
// 顯示提示框
}
}
我可以這樣讓我的提示框顯示一個(gè)較長(zhǎng)的時(shí)間蛾派。
MyTip.show("message", MyTip.LONG_TIME);
但是有個(gè)問(wèn)題读第,這里我傳入的參數(shù)是MyTip.LONG_TIME
们妥,但是實(shí)際上不管傳入的是1還是0,甚至是MyTip.show("message", 2);
都不會(huì)提示錯(cuò)誤淮腾,因?yàn)橹灰莍nt就可以,這顯示不是我想要的屉佳。這里我們就需要用到枚舉注解了谷朝,@IntDef和@StringDef
@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;
}
這時(shí)候我再修改一下代碼
public class MyTip {
public static final int LONG_TIME = 0;
public static final int SHORT_TIME = 1;
@IntDef({LONG_TIME, SHORT_TIME})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
public static void show(String message, @Type int type) {
// 顯示提示框
}
}
這里自己寫(xiě)了一個(gè)注解@Type,并且使用了@IntDef注解武花,value就是兩個(gè)靜態(tài)常量圆凰。這時(shí)候再看調(diào)用的地方。
是不是非常熟悉体箕?沒(méi)錯(cuò)专钉,我們熟悉的Toast就是用@IntDef注解這么實(shí)現(xiàn)的挑童。感興趣的可以去看看源碼。
總結(jié)
發(fā)現(xiàn)注解是一個(gè)非常有意思的東西跃须,他可以讓我們?cè)诤茉绲臅r(shí)候就發(fā)現(xiàn)問(wèn)題站叼,團(tuán)隊(duì)協(xié)作起來(lái)也更有效率,所以做了一些總結(jié)菇民,希望以后在項(xiàng)目中能夠多多用到注解尽楔。