首先帶入問題岳守。
1.什么是注解。
2.注解有什么用碌冶,我們?yōu)槭裁匆米⒔猓?/h5>
3.注解的生命周期湿痢,編譯時(shí)注解和運(yùn)行時(shí)注解區(qū)別。
引用別人對(duì)注解的解釋,注解可以理解成標(biāo)簽譬重。
在代碼中我們最常見的注解應(yīng)該是他 @Override 拒逮,用于重載父類的方法。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
引用別人對(duì)注解的解釋,注解可以理解成標(biāo)簽譬重。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
點(diǎn)擊 @Override 進(jìn)到源碼臀规,中我們會(huì)發(fā)現(xiàn)下面這種結(jié)構(gòu)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
那么滩援,參考源碼的實(shí)現(xiàn)方式我們這樣就可以生成自己的注解
public @interface TestAnnotation {
}
在我們代碼中進(jìn)行調(diào)用
@TestAnnotation
private void logOut() {
Log.i(TAG, "logOut: ");
}
只不過這種注解實(shí)現(xiàn),除了能增加代碼量塔嬉,其他毫無意義 [手動(dòng)狗頭]玩徊。
上面@Override 注解中出現(xiàn)的 @Target 和 @Retention
這兩個(gè)東西,一看感覺就是注解的參數(shù)邑遏,點(diǎn)進(jìn)源碼會(huì)發(fā)現(xiàn)官方給他們起了一個(gè)專門的名字佣赖,meta-annotation(元注解),其只能记盒,表明聲明的類型憎蛤,僅用作復(fù)雜注釋類型聲明中的成員類型。它不能用于直接注釋任何內(nèi)容纪吮。(意思對(duì)注解進(jìn)行注解)
/**
* <p>This {@code @Target} meta-annotation indicates that the declared type is
* intended solely for use as a member type in complex annotation type
* declarations. It cannot be used to annotate anything directly:
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
如果我們?cè)谄渌牡胤秸{(diào)用俩檬,as會(huì)彈出類似的報(bào)錯(cuò)。
元注解是注解中的一種碾盟,他通過 @Target(ElementType.ANNOTATION_TYPE) 指定其使用場(chǎng)景棚辽,只能用在注解上,即對(duì)注解進(jìn)行注解
Target 支持的類型
ElementType | 類型范圍 |
---|---|
TYPE | 類冰肴、接口(包括注釋類型)或枚舉聲明 |
FIELD | 字段聲明(包括枚舉常量 |
METHOD | 方法 |
PARAMETER | 參數(shù) |
CONSTRUCTOR | 構(gòu)造方法 |
LOCAL_VARIABLE | 局部變量 |
ANNOTATION_TYPE | 注解 |
PACKAGE | 包 |
TYPE_PARAMETER | 類型參數(shù)(泛型) |
TYPE_USE | 使用類型注解 |
@Retention 指定注解的生命周期
SOURCE | CLASS | RUNTIME | |
---|---|---|---|
生命周期 | 源碼階段 | Class文件階段 | 運(yùn)行 |
解釋 | .java文件(僅在我們開發(fā)過程中存在, 提示錯(cuò)誤或警告) |
編譯后從java變成.class文件 保存在字節(jié)碼 |
ClassLoder 加載class字節(jié)碼到內(nèi)存 |
那么對(duì)第一個(gè)問題的總結(jié) 什么是注解屈藐,在代碼層面上,只要使用@interface 的都是注解熙尉。如果我們指定他的Target在annotation上联逻。@Target(ElementType.ANNOTATION_TYPE) 那么也可以稱他為元注解。
2 注解的作用
- @Retention(RetentionPolicy.SOURCE)
源代碼時(shí)期的注解检痰,僅存在于.java文件中
1.用于代碼檢查例如@NonNull /@Nullable 包归,資源引用限制例如@DrawableRes,@StringRes铅歼,提醒錯(cuò)誤公壤,過時(shí)例如@Deprecated。
2.在編譯期間處理椎椰,.java文件編譯生成class文件時(shí)期厦幅,通過開發(fā)者注冊(cè)的注解處理器(AnnotationProcessor)對(duì)注解進(jìn)行處理,使用JavaPoet生成模板代碼慨飘,提高開發(fā)效率确憨。 - @Retention(RetentionPolicy.RUNTIME)
1.注解在class字節(jié)碼文件中存在,通過反射獲取到注解的對(duì)象以及參數(shù)等信息提供調(diào)用。容易實(shí)現(xiàn)但反射消耗性能不建議過度使用缚态。
舉個(gè)栗子
完善一下我們之前的TestAnnotation 添加兩個(gè)參數(shù),name 和 age
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String name();
int age();
}
在創(chuàng)建一個(gè)UserBean類
public class UserBean {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private String getName() {
return name;
}
@FunctionAnnotation
private void setName(@FunctionType String name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
+ "name='" + name + '\''
+ ", age=" + age
+ '}';
}
}
創(chuàng)建用于綁定的Utils
public class AnnotationUtils {
public static void Inject(Activity activity) {
//萬物皆對(duì)象。獲取activity的class
Class<? extends Activity> c = activity.getClass();
// 獲取所有字段
Field[] fields = c.getDeclaredFields();
//遍歷拿到帶有 @TestAnnotation 注解的字段
for (Field field : fields) {
if (field.isAnnotationPresent(TestAnnotation.class)) {
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if (annotation == null) {
break;
}
//TestAnnotation name
String name = annotation.name();
int old = annotation.age();
//類型的包名路徑 例如:com.example.demo.data.UserBean
String packName = field.getType().getName();
//手動(dòng)定義的對(duì)象名稱堤瘤,此例子使用的是mUser
String fieldName = field.getName();
try {
//創(chuàng)建一個(gè)user 這里使用包名路徑
Class<?> fieldClass = Class.forName(packName);
UserBean user = (UserBean) fieldClass.newInstance();
//返回此Class對(duì)象對(duì)應(yīng)類的玫芦、帶指定形參列表的方法
Method declaredMethod = fieldClass.getDeclaredMethod("setName", String.class);
//設(shè)置可以訪問私有權(quán)限
declaredMethod.setAccessible(true);
//調(diào)用方法
declaredMethod.invoke(user, name);
Method declaredMethodSetOld = fieldClass.getDeclaredMethod("setAge", int.class);
declaredMethodSetOld.setAccessible(true);
declaredMethodSetOld.invoke(user, old);
//設(shè)置可以訪問私有權(quán)限
field.setAccessible(true);
//set對(duì)象
field.set(activity,user);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
MainActivity中使用AnnotationUtils注入U(xiǎn)serBean 對(duì)象
public class MainActivity extends Activity {
@TestAnnotation(name = "xiao ming", old = 18)
private UserBean mUser;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotationUtils.Inject(this);
System.out.println(" main activity user is " + mUser);
}
}
運(yùn)行程序我們會(huì)得到如下的日志打印
I/System.out: main activity user is User{name='xiao ming', age=18}
注解的作用,小結(jié)
emmm本辐,搞了一圈你就給我看這個(gè)桥帆?我直接new一個(gè)對(duì)象set進(jìn)屬性不就完了嗎。搞這么一圈四不四有毛病慎皱。
對(duì)于我們自己寫代碼的話確實(shí)如此老虫。而且代碼業(yè)務(wù)越簡(jiǎn)單/單一,越不需要反射和注解茫多。如果只要實(shí)現(xiàn)一句hello world祈匙,那搞別的操作真是畫蛇添足。
我理解是這樣的
反射 的場(chǎng)景在于天揖,我們需要使用別人的代碼或者android源碼(依賴庫)夺欲。且無法直接修改,或者調(diào)用今膊,別人寫的代碼的情況些阅。迫不得已 我們可以嘗試使用反射去操作修改別人的對(duì)象,或者調(diào)用方法斑唬。
注解嘛市埋,就厲害了。我們需要給別人提供服務(wù)恕刘,或者依賴庫的時(shí)候缤谎。使用者通過使用,我們開發(fā)者定義的注解雪营,按照我們定義的規(guī)則去標(biāo)注使用者的代碼弓千,從而為開發(fā)者實(shí)現(xiàn)某些功能。方便使用者使用献起,和理解洋访。
例如:retrofit ,我們就是使用者谴餐,按照retrofit開發(fā)者定義好的各種注解姻政,例如@GET,@POST岂嗓,按照retrofit開發(fā)者定義的規(guī)則去標(biāo)注我們的接口方法汁展。開發(fā)者通過讀取我們的注解,幫我們實(shí)現(xiàn)各種邏輯。其他使用注解的框架同理食绿,都是為了使用者便方便調(diào)用侈咕。
那么如果我們,需要給別人提供服務(wù)器紧,或者我們要寫依賴庫的時(shí)候耀销,就可以考慮是否用注解去實(shí)現(xiàn)了。如果上面說的铲汪,理解了熊尉。那應(yīng)該也會(huì)理解,為什么上面舉的栗子掌腰,明明只用反射也可以實(shí)習(xí)給user 設(shè)置 name 和 age狰住,卻偏偏要用注解了吧。(先后關(guān)系齿梁,先有我們定義好注解的TestAnnotation注解的規(guī)則催植,后面才有開發(fā)者使用TestAnnotation 注解去標(biāo)注給User使用)。
注解的優(yōu)勢(shì)
- 上面的栗子運(yùn)行時(shí)期注解士飒,需要用到反射去完成各種操作查邢,好像體現(xiàn)不出注解的優(yōu)勢(shì)。
- 源碼時(shí)期注解提示安全以及報(bào)錯(cuò)
- 編譯時(shí)期注解酵幕,在編譯階段可以為我們生成代碼扰藕,實(shí)現(xiàn)各種功能例如早期的 JakeWharton 大神Butterknife ,生成代碼實(shí)現(xiàn)findviewById操作芳撒。以及Goodle HIlt 依賴注入邓深。
那我們?nèi)绾螌?shí)現(xiàn)編譯時(shí)注解呢。Javapoet為此做出了很大貢獻(xiàn)笔刹。 https://github.com/square/javapoet 具體使用方法請(qǐng)搜索引擎芥备,輸入Javapoet教學(xué) [手動(dòng)狗頭]
對(duì)于第三個(gè)問題,注解的生命周期舌菜,編譯時(shí)注解和運(yùn)行時(shí)注解區(qū)別
你都看到這了萌壳,應(yīng)該有個(gè)大致答案了。
- 首先聲明周期就不一樣日月,一個(gè)在編譯期class字節(jié)碼階段袱瓮。一個(gè)被ClassLoder 從class字節(jié)碼到內(nèi)存。
- 編譯時(shí)期注解一般會(huì)生成各種代碼實(shí)現(xiàn)各種操作爱咬。運(yùn)行時(shí)期注解尺借,程序運(yùn)行時(shí)期獲取到你使用的各種注解,例如retrofit 運(yùn)行時(shí)注解 加 動(dòng)態(tài)代理完成網(wǎng)絡(luò)請(qǐng)求的各種操作精拟。
- //todo以后想到了繼續(xù)補(bǔ)充