爲何要寫這一系列的博客呢?java
由於在 Android 開發的過程當中, 泛型,反射,註解這些知識進場會用到,幾乎全部的框架至少都會用到上面的一兩種知識,如 Gson 就用到泛型,反射,註解,Retrofit 也用到泛型,反射,註解 。學好這些知識對咱們進階很是重要,尤爲是閱讀開源框架源碼或者本身開發開源框架。android
java Type 詳解程序員
註解使用入門(一)bash
通過前面的介紹,相信你們對註解有了必定的瞭解了。ui
根據註解使用方法和用途,咱們能夠將Annotation分爲三類:spa
元註解 解析說明
@Documented 是否會保存到 Javadoc 文檔中
@Retention 保留時間,可選值, 默認爲 CLASS
SOURCE(源碼時),CLASS(編譯時),RUNTIME(運行時)
@Target 能夠用來修飾哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標註則表示可修飾全部
ANONOTATION_TYPE(註解類型聲明),
PACKAGE(包)
TYPE (類,包括enum及接口,註解類型)
METHOD (方法)
CONSTRUCTOR (構造方法)
FIFLD (成員變量)
PARAMATER (參數)
LOCAL_VARIABLE (局部 變量)
複製代碼
須要注意的是註解是不能夠繼承的,@Inherited 的意思是 加入咱們把註解應用到 A 類中,B 類繼承 A ,那麼 B 能夠掃描到 A 的註解。
這裏講的繼承並非經過@Inherited修飾的註解。
這個「繼承」是一個註解的使用技巧,使用上的感受相似於依賴倒置,來自於ButterKnife源碼。
先看代碼。
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
int[] value() default { View.NO_ID };
}
複製代碼
這是ButterKnife的OnClick 註解。特殊的地方在於@OnClick修飾了註解@ListenerClass,而且設置了一些只屬於@OnClick的屬性。
那這樣的做用是什麼呢?
凡是修飾了@OnClick的地方,也就自動修飾了@ListenerClass。相似於@OnClick是@ListenerClass的子類。而ButterKnife有不少的監聽註解@OnItemClick、@OnLongClick等等。
這樣在作代碼生成時,不須要再單獨考慮每個監聽註解,只須要處理@ListenerClass就OK。如 @interface OnItemClick 等。
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.widget.AdapterView<?>",
setter = "setOnItemClickListener",
type = "android.widget.AdapterView.OnItemClickListener",
method = @ListenerMethod(
name = "onItemClick",
parameters = {
"android.widget.AdapterView<?>",
"android.view.View",
"int",
"long"
}
)
)
public @interface OnItemClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
複製代碼
一個簡單的自定義註解例子
@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Seriable {
}
複製代碼
指定默認值
@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Seriable {
int id();
String name() default "test";
}
//使用
@Seriable(id = 1) //name有默認值能夠不寫
class Test{
}
複製代碼
關於怎樣自定義一個註解,能夠參看這一篇博客,Android 自定義編譯時註解1 - 簡單的例子
自定義註解後,須要編寫Processor類處理註解。Processor 繼承自 AbstractProcessor 的類。咱們主要須要處理如下連個方法。
public Set getSupportedAnnotationTypes()
public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
重寫 getSupportedAnnotationTypes 方法:告知Processor哪些註解須要處理。返回一個Set集合,集合內容爲 自定義註解的包名+類名。
建議項目中這樣編寫:
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
//須要全類名
types.add(Seriable.class.getCanonicalName());
types.add(Println.class.getCanonicalName());
return types;
}
複製代碼
另外若是註解數量不多的話,能夠經過另外一種方式實現:
//在只有一到兩個註解須要處理時,能夠這樣編寫:
@SupportedAnnotationTypes({"com.example.Seriable"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class JsonProcessor extends AbstractProcessor {
}
複製代碼
process 方法,這個方法是全部方法中最關鍵的一個方法,他用來處理註解的相關信息,好比提取註解的信息,存進 map 集合或者生成代碼等。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
複製代碼
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 第一步,根據咱們自定義的註解拿到 elememts set 集合
Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
TypeElement typeElement;
VariableElement variableElement;
// 第二步: 根據 element 的類型作相應的處理,並存進 map 集合
for (Element element : elememts) {
ElementKind kind = element.getKind();
// 判斷該元素是否爲類
if (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element;
// 判斷該元素是否爲成員變量
} else if (kind == ElementKind.FIELD) {
variableElement = (VariableElement) element;
}
}
return true;
}
複製代碼
元素,雖有經過註解取得的元素都以 Element 等待處理,它的具體類型與咱們經過 @Target 來標記的具備必定的聯繫。
官方的解釋是這樣的:
Represents a program element such as a package, class, or method.Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine)
表示程序元素如包、類或者方法。每一個元素表明一個靜態語言級構造(例如,而不是運行時構建的虛擬機)
例如:
// 第一步,根據咱們自定義的註解拿到 elememts set 集合
Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
TypeElement typeElement;
VariableElement variableElement;
// 第二步: 根據 element 的類型作相應的處理,並存進 map 集合
for (Element element : elememts) {
ElementKind kind = element.getKind();
// 判斷該元素是否爲類
if (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element;
// 判斷該元素是否爲成員變量
} else if (kind == ElementKind.FIELD) {
variableElement = (VariableElement) element;
}
}
複製代碼
Element 的子類有:
表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註釋類型元素。
對應@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)
表示一個包程序元素。提供對有關包極其成員的信息訪問。
對應@Target(ElementType.PACKAGE)
表示一個類或接口程序元素。提供對有關類型極其成員的信息訪問。
對應@Target(ElementType.TYPE)
注意:枚舉類型是一種類,而註解類型是一種接口。
表示通常類、接口、方法或構造方法元素的類型參數。
對應@Target(ElementType.PARAMETER)
表示一個字段、enum常量、方法或構造方法參數、局部變量或異常參數。
對應 @Target(ElementType.LOCAL_VARIABLE)
咱們能夠經過 element.getKind(); 來獲得 Element 究竟是哪種類別,該方法返回 ElementKind 類型,咱們在根據 ElementKind 便可得出 Element 究竟是哪一種類別。
ElementKind kind = element.getKind();
// 判斷該元素是否爲類
if (kind == ElementKind.CLASS) {
typeElement = (TypeElement) element;
} else if (kind == ElementKind.FIELD) {
variableElement = (VariableElement) element;
}
複製代碼
看到這裏,想起前面的一篇博客 java Type 詳解 ,咱們須要注意 Type 與 Element 的區別。
Type 是用來處理泛型的,Element 是用來處理註解的。
註解語法暫時介紹到這裏,之後想到新的,會逐步更新到這篇博客,下一篇,將分析 ButterKnife 源碼。
掃一掃,歡迎關注個人微信公衆號 stormjun94(徐公碼字), 目前是一名程序員,不只分享 Android開發相關知識,同時還分享技術人成長曆程,包括我的總結,職場經驗,面試經驗等,但願能讓你少走一點彎路。