Android 進(jìn)階-注解(Annotation)

1.了解注解

解析:注解是一種元數(shù)據(jù), 可以添加到j(luò)ava代碼中. 類污筷、方法工闺、變量悠鞍、參數(shù)咒程、包都可以被注解夺溢,注解對注解的代碼沒有直接影響. ?在Java文檔中的定義如下:

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

簡單來說吃警,在Android中主要用途有:

1.起標(biāo)識的作用,和Android Studio一起提示警告信息古胆,可以快捷疗认、安全有效地編寫代碼清笨;

2.運(yùn)行時(shí)注解:指的是運(yùn)行階段利用反射器腋,動態(tài)獲取被標(biāo)記的方法溪猿、變量等钩杰,如EvenBus纫塌。

3.編譯時(shí)注解:指的是程序在編譯階段會根據(jù)注解進(jìn)行一些額外的處理,如ButterKnife讲弄。運(yùn)行時(shí)注解和編譯時(shí)注解措左,都可以理解為通過注解標(biāo)識,然后進(jìn)行相應(yīng)處理避除,兩者的區(qū)別是:前者是運(yùn)行時(shí)執(zhí)行的怎披,反射的使用會降低性能胸嘁;后者是編譯階段執(zhí)行的,通過生成輔助類實(shí)現(xiàn)效果凉逛。

注意:注解本身不會影響代碼的運(yùn)行性宏,以ButterKnife為例,注解之所以生效是因?yàn)锽utterKnife是在標(biāo)識后內(nèi)部進(jìn)行了處理状飞。

下面先看看第一種用途:

2.注解的基本使用

Java內(nèi)置的注解有Override, Deprecated, SuppressWarnings毫胜,我們都知道Override是用來標(biāo)識重寫的,Deprecated是標(biāo)識廢棄的诬辈,SuppressWarnings是告訴編譯器忽略指定的警告的酵使。而Android中,在android.support.annotaion包下定義了很多注解(數(shù)了一下焙糟,目前共有44個(gè))口渔,下面就常見的注解舉例說明:

1.@NonNull,一般用來標(biāo)識不為空。


上圖中穿撮,用@NonNull標(biāo)識參數(shù)temp不為null缺脉,當(dāng)在方法體中判斷temp是否為null,編譯器AS就會給出提示“temp不會是null的”悦穿。當(dāng)我們在外部調(diào)用這個(gè)方法的時(shí)候枪向,傳入一個(gè)為空的參數(shù)時(shí),AS也會給出提示咧党。需要注意的一點(diǎn)是秘蛔,AS不是什么時(shí)候都能檢測出傳入的參數(shù)可能為空的,例如經(jīng)過較復(fù)雜的處理后傍衡,參數(shù)可能在過程中被設(shè)為空了深员。到這里不禁就要問了,既然沒法百分百保證傳入的參數(shù)不為空蛙埂,那這個(gè)@NonNull的意義還大嗎倦畅?筆者認(rèn)為雖然不能百分百保證,但在大多數(shù)情況下绣的,確實(shí)能幫助我們在編寫代碼的時(shí)候就趁早發(fā)現(xiàn)問題叠赐,當(dāng)然在較復(fù)雜下,在外部調(diào)用的時(shí)候屡江,當(dāng)我們沒法保證傳入的值是否為空芭概,還是有必要判斷一下再調(diào)用這個(gè)方法。

2.資源注解:如@StringRes惩嘉、@ColorRes罢洲、@StyleRes、@DrawableRes

定義一個(gè)方法文黎,用@StringRes標(biāo)識參數(shù),

private String getContent(@StringResintresId){

return getString(resId);

}

當(dāng)我們引用的時(shí)候惹苗,如果不小心傳入了非String的資源id殿较,如getContent(R.color.colorAccent),編譯器就會報(bào)錯(cuò)“錯(cuò)誤的資源類型”,如果沒有使用注解,這個(gè)錯(cuò)誤直到運(yùn)行才會被發(fā)現(xiàn)桩蓉,這些注解的使用能讓我們避免或者更早發(fā)現(xiàn)問題淋纲。

以上是注解用途一的簡單使用,在了解運(yùn)行時(shí)注解和編譯時(shí)注解前院究,我們需要先知道如何自定義注解帚戳,以及如何使用自定義的注解;

3.自定義注解

首先以Java中已有的注解@Override為例儡首,說明一下是如何定義注解的片任,@Override的源碼如下:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface Override{

}

可以看到,用關(guān)鍵字@interface聲明注解,前兩行的@Target和@Retention也是注解蔬胯,準(zhǔn)確來說是元注解(元注解就是用來定義注解的注解)对供,@Target用來指定注解Overrider是用于修飾哪些元素,這里的@Target(ElementType.METHOD)具體指的是Override是用來修飾方法的氛濒,不是用來修飾字段的产场;@Retention是指定保留策略的,這里的@Retention(RetentionPolicy.SOURCE)具體指的是Override注解只有在源碼中可用舞竿,在字節(jié)碼或者運(yùn)行時(shí)不可用京景,也就是說如果只需要在編寫代碼階段注解生效,一般設(shè)置為@Retention(RetentionPolicy.SOURCE)骗奖,如果希望在代碼運(yùn)行時(shí)注解生效确徙,則需要設(shè)置為@Retention(RetentionPolicy.RUNTIME)。

現(xiàn)在對注解有了個(gè)大概的了解执桌,那么我們開始仿照Override定義一個(gè)注解吧:

@Target(ElementType.METHOD)//指定該注解只能用于標(biāo)識方法

@Retention(RetentionPolicy.SOURCE)//指定保留策略為在源碼中可用

public @interface MyAnnotation{

//do nothing

}

如何使用鄙皇?在方法前直接添加@MyAnnotation就可以了,到這里就完成了注解的定義和使用啦仰挣!

@MyAnnotation

private String getContent(int resId){

return getString(resId);

}

如果是在變量前添加這個(gè)注解伴逸,編譯器是會報(bào)錯(cuò)的,因?yàn)槲覀冎付嗽撟⒔庵荒苡糜跇?biāo)識方法膘壶!上面說了注解本身是不會造成影響的错蝴,所以這個(gè)MyAnnotation并沒有什么卵用。

現(xiàn)在我們要實(shí)現(xiàn)這樣一個(gè)功能:用MyAnnotation標(biāo)識一個(gè)字段颓芭,并為這個(gè)字段設(shè)置一個(gè)默認(rèn)值顷锰。來來來,修改一下MyAnnotation:

@Target(ElementType.FIELD)//指定用于標(biāo)識字段

@Retention(RetentionPolicy.RUNTIME)//指定保留策略為運(yùn)行時(shí)可用

public @interface MyAnnotation{

String value(); ? //用于保存默認(rèn)值

}

當(dāng)在注解中添加了value()方法后畜伐,那么使用的時(shí)候就從 @MyAnnotation 變成了 @MyAnnotation("Test Annotation")馍惹,沒錯(cuò)躺率,這里的value對應(yīng)的就是“Test Annotation”玛界,如下:

@MyAnnotation("Test Annotation")

String temp;

到這里万矾,注解還是沒有任何效果的,我們期望的是 temp的值就是“Test Annotation”慎框,因此需要在初始化的時(shí)候做點(diǎn)事情良狈,即把這個(gè)值“Test Annotation”賦給變量temp,這里使用反射來處理這個(gè)過程:

private void initMyAnnotation(Activity activity){

try{

//獲取要解析的類

Class cls = Class.forName("com.example.test.Main3Activity");

//拿到所有Field

Field[] declaredFields = cls.getDeclaredFields();

for(Field field : declaredFields){

//獲取Field上的注解

MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);

//如果這個(gè)Field有注解

if(annotation !=null){

//獲取注解上的值

String value = annotation.value();

//將值設(shè)置給該字段

field.setAccessible(true);

field.set(activity,value);

}

}

}catch(ClassNotFoundException e) {

e.printStackTrace();

}catch(IllegalAccessException e) {

e.printStackTrace();

}

}

最后在activity的onCreate方法里加上這句:至此我們就完成了通過MyAnnotation為字段設(shè)置一個(gè)默認(rèn)值的功能了笨枯。

initMyAnnotation(this);

以上就是注解在Android的第二種用途:運(yùn)行時(shí)注解薪丁,也就是通過反射獲得被注解的字段方法,然后再自己處理馅精。

看到這里严嗜,是不是覺得我們也能實(shí)現(xiàn)類似于ButterKnife的功能啦(ButterKnife的原理不是使用反射的),說干就干:

首先定義一個(gè)注解ViewAnnotation:

@Target(ElementType.FIELD) ?

@Retention(RetentionPolicy.RUNTIME)

public@interfaceViewAnnotation{

intvalue();

}

再聲明一個(gè)方法處理賦值:

public static void bindView(Activity activity) {

Class aClass = activity.getClass();

//獲取activity所有字段

Field[] fields = aClass.getDeclaredFields();

//得到被ViewInject注解的字段

for(Field field : fields) {

if(field.isAnnotationPresent(ViewAnnotation.class)) {

//得到字段的ViewInject注解

ViewAnnotationviewInject = field.getAnnotation(ViewAnnotation.class);

//得到注解的值

intviewId = viewInject.value();

//使用反射調(diào)用findViewById洲敢,并為字段設(shè)置值

try{

Method method = aClass.getMethod("findViewById", int.class);

method.setAccessible(true);

Object resView = method.invoke(activity,viewId);

field.setAccessible(true);

field.set(activity,resView);

}catch(NoSuchMethodException e) {

e.printStackTrace();

}catch(InvocationTargetException e) {

e.printStackTrace();

}catch(IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

最后記得在初始化時(shí)調(diào)用bindView(this)漫玄。

Ok,以上說的都是基于運(yùn)行時(shí)注解的压彭,開頭說了睦优,還有一種編譯時(shí)注解,不得不再次祭出ButterKnife了壮不,網(wǎng)上有很多剖析ButterKnife原理和源代碼的文章了汗盘,這里就不多廢話了,本文就到這里了询一,水平有限隐孽,不當(dāng)之處請指出,謝謝健蕊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缓醋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绊诲,更是在濱河造成了極大的恐慌送粱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掂之,死亡現(xiàn)場離奇詭異抗俄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)世舰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門动雹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跟压,你說我怎么就攤上這事胰蝠。” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵茸塞,是天一觀的道長躲庄。 經(jīng)常有香客問我,道長钾虐,這世上最難降的妖魔是什么噪窘? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮效扫,結(jié)果婚禮上倔监,老公的妹妹穿的比我還像新娘。我一直安慰自己菌仁,他們只是感情好浩习,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著济丘,像睡著了一般瘦锹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闪盔,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天弯院,我揣著相機(jī)與錄音,去河邊找鬼泪掀。 笑死听绳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的异赫。 我是一名探鬼主播椅挣,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼塔拳!你這毒婦竟也來了鼠证?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤靠抑,失蹤者是張志新(化名)和其女友劉穎量九,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颂碧,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荠列,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了载城。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌似。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诉瓦,靈堂內(nèi)的尸體忽然破棺而出川队,到底是詐尸還是另有隱情力细,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布固额,位于F島的核電站眠蚂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏对雪。R本人自食惡果不足惜河狐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一米绕、第九天 我趴在偏房一處隱蔽的房頂上張望瑟捣。 院中可真熱鬧,春花似錦栅干、人聲如沸迈套。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桑李。三九已至,卻和暖如春窿给,著一層夾襖步出監(jiān)牢的瞬間贵白,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工崩泡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禁荒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓角撞,卻偏偏與公主長得像呛伴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子谒所,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容