Android中註解的詳細介紹
0在註解中主要的概念
1在獲取註解時用到了反射 2註解的流程:定義註解,實現註解,使用註解。 3在獲取註解資料時,類似於獲取普通資料。1什麼是註解
註解(Annotation)是JDK1.5引入的新特性,包含在Java.lang.annotation包中,它是附加在程式碼中的一些元資訊,將一個類的外部資訊與內部成員聯絡起來,在編 譯、執行時進行解析和使用。Java內建了一些註解(如@Override、@Deprecated等),還支援自定義註解,一些知名的框架
Struts、hibernate、spring都有自己實現的自定義註解,也可以自己定義註解供使用。
Annotation十分類似public、final這樣的修飾符。每個Annotation具有一個名字和成員個數>=0。每個Annotation的成員具有被稱為name=value對的名字和值(就像javabean一樣),name=value裝載了Annotation的資訊。
2註解的基本介紹:
註解的用處:
1、生成文件。這是最常見的,也是java 最早提供的註解。常用的有@param @return 等
2、跟蹤程式碼依賴性,實現替代配置檔案功能。比如Dagger 2依賴注入,未來java開發,將大量註解配置,具有很大用處;
3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出。
元註解:
java.lang.annotation提供了四種元註解,專門註解其他的註解:
@Documented –註解是否將包含在JavaDoc中
@Retention
@Target –註解用於什麼地方
@Inherited – 是否允許子類繼承該註解
1.)@Retention– 定義該註解的生命週期
- RetentionPolicy.SOURCE : 在編譯階段丟棄。這些註解在編譯結束之後就不再有任何意義,所以它們不會寫入位元組碼。@Override, @SuppressWarnings都屬於這類註解。
- RetentionPolicy.CLASS : 在類載入的時候丟棄。在位元組碼檔案的處理中有用。註解預設使用這種方式
- RetentionPolicy.RUNTIME : 始終不會丟棄,執行期也保留該註解,因此可以使用反射機制讀取該註解的資訊。我們自定義的註解通常使用這種方式。
舉例:bufferKnife 8.0 中@BindView 生命週期為CLASS
@Retention(CLASS) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }
2.)Target – 表示該註解用於什麼地方。預設值為任何元素,表示該註解用於什麼地方。可用的ElementType引數包括
- ElementType.CONSTRUCTOR:用於描述構造器
- ElementType.FIELD:成員變數、物件、屬性(包括enum例項)
- ElementType.LOCAL_VARIABLE:用於描述區域性變數
- ElementType.METHOD:用於描述方法
- ElementType.PACKAGE:用於描述包
- ElementType.PARAMETER:用於描述引數
- ElementType.TYPE:用於描述類、介面(包括註解型別) 或enum宣告
舉例Retrofit 2 中@Field 作用域為引數
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface Field { String value(); /** Specifies whether the {@linkplain #value() name} and value are already URL encoded. */ boolean encoded() default false; }
3.)@Documented–一個簡單的Annotations標記註解,表示是否將註解資訊新增在java文件中。
4.)@Inherited – 定義該註釋和子類的關係
@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的型別是被繼承的。如果一個使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類。
常見標準的Annotation:
1.)Override
java.lang.Override是一個標記型別註解,它被用作標註方法。它說明了被標註的方法過載了父類的方法,起到了斷言的作用。如果我們使用了這種註解在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。
2.)Deprecated
Deprecated也是一種標記型別註解。當一個型別或者型別成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標註的程式元素。所以使用這種修飾具有一定的“延續性”:如果我們在程式碼中通過繼承或者覆蓋的方式使用了這個過時的型別或者成員,雖然繼承或者覆蓋後的型別或者成員並不是被宣告為@Deprecated,但編譯器仍然要報警。
3.)SuppressWarnings
SuppressWarning不是一個標記型別註解。它有一個型別為String[]的成員,這個成員的值為被禁止的警告名。對於javac編譯器來講,被-Xlint選項有效的警告名也同樣對@SuppressWarings有效,同時編譯器忽略掉無法識別的警告名。
@SuppressWarnings("unchecked")
自定義註解:
這裡模擬一個滿足網路請求介面,以及如何獲取介面的註解函式,引數執行請求。
1.)定義註解:
@ReqType 請求型別
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqType { /** * 請求方式列舉 * */ enum ReqTypeEnum{ GET,POST,DELETE,PUT}; /** * 請求方式 * @return */ ReqTypeEnum reqType() default ReqTypeEnum.POST; }
@ReqUrl 請求地址
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqUrl { String reqUrl() default ""; }
@ReqParam 請求引數
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface ReqParam { String value() default ""; }
從上面可以看出註解引數的可支援資料型別有如下:
1.所有基本資料型別(int,float,boolean,byte,double,char,long,short)
2.String型別
3.Class型別
4.enum型別
5.Annotation型別
6.以上所有型別的陣列
而且不難發現@interface用來宣告一個註解,其中的每一個方法實際上是聲明瞭一個配置引數。方法的名稱就是引數的名稱,返回值型別就是引數的型別(返回值型別只能是基本型別、Class、String、enum)。可以通過default來宣告引數的預設值。
2.)如何使用自定義註解
public interface IReqApi { @ReqType(reqType = ReqType.ReqTypeEnum.POST)//宣告採用post請求 @ReqUrl(reqUrl = "www.xxx.com/openApi/login")//請求Url地址 String login(@ReqParam("userId") String userId, @ReqParam("pwd") String pwd);//引數使用者名稱 密碼 }
3.)如何獲取註解引數
這裡強調一下,Annotation是被動的元資料,永遠不會有主動行為,但凡Annotation起作用的場合都是有一個執行機制/呼叫者通過反射獲得了這個元資料然後根據它採取行動。
通過反射機制獲取函式註解資訊
Method[] declaredMethods = IReqApi.class.getDeclaredMethods(); for (Method method : declaredMethods) { Annotation[] methodAnnotations = method.getAnnotations(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations(); }
也可以獲取指定的註解
ReqType reqType =method.getAnnotation(ReqType.class);
4.)具體實現註解介面呼叫
這裡採用Java動態代理機制來實現,將定義介面與實現分離開,這個後期有時間再做總結。
private void testApi() { IReqApi api = create(IReqApi.class); api.login("whoislcj", "123456"); } public <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable {// Annotation[] methodAnnotations = method.getAnnotations();//拿到函式註解陣列 ReqType reqType = method.getAnnotation(ReqType.class); Log.e(TAG, "IReqApi---reqType->" + (reqType.reqType() == ReqType.ReqTypeEnum.POST ? "POST" : "OTHER")); ReqUrl reqUrl = method.getAnnotation(ReqUrl.class); Log.e(TAG, "IReqApi---reqUrl->" + reqUrl.reqUrl()); Type[] parameterTypes = method.getGenericParameterTypes(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();//拿到引數註解 for (int i = 0; i < parameterAnnotationsArray.length; i++) { Annotation[] annotations = parameterAnnotationsArray[i]; if (annotations != null) { ReqParam reqParam = (ReqParam) annotations[0]; Log.e(TAG, "reqParam---reqParam->" + reqParam.value() + "==" + args[i]); } } //下面就可以執行相應的網路請求獲取結果 返回結果 String result = "";//這裡模擬一個結果 return result; } }); }