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)之處請指出,謝謝健蕊。