Android註解(Annotation)知識點總結整理
Java提供了Annotations來對程式碼提供一些註解,以解釋、約束程式碼,也可以稱為程式設計的元資料。到底什麼是Annotation呢,舉幾個大家熟悉的例子如@Override(表示過載)、@Deprecated(表示下述方法不推薦使用,通常標註為此會提供推薦方法)等,Butter Knife也是Android開發常用的庫,GitHub上Star達到了6200+,它在簡化程式碼時主要用到的也是註解技巧。
@Override public void onCreate(Bundle savedInstanceState);Butter Knife @Bind(R.id.user) EditText username;
之所以叫Android Annotations,是因為Android的註解比Java多提供了一個庫“android.support:support-annotations”,可以說是Java註解的擴充套件。接下來本文經過查證參考,總結整理了其基本的概念,因為翻譯水平有限,涉及定義的地方將對文件原文有所引用。望各位批評指正~
- 定義
先摘錄Wiki和Oracle的原文定義如下:
Wiki
An annotation, in the Java computer programming language, is a form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Unlike Javadoc tags, Java annotations can be reflective in that they can be embedded in class files generated by the compiler and may be retained by the Java VM to be made retrievable at run-time. It is possible to create meta-annotations out of the existing ones in Java.
Oracle
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
Annotation,中文一般譯為“註解”,表示為程式語言的元資料格式,可以新增如Java的原始碼中,類、方法、變數、引數、包都可以被註解。不同於Javadoc的標籤,Java的註解如果設定為留存到VM可識別的執行時態,那麼它是可以通過反射獲取出來的。註解本身對他們所註解的程式碼沒有直接的影響。
- 型別
很多資料對Java Annotation進行了分類,大部分是分為:Java內建、元註解、自定註解。這裡因為考慮到Android庫提供的註解,我將其總結為以下幾種型別:
Built-in Java Annotations(java.lang) Meta-annotations(java.lang.annotation) Android Support Annotations(android.support:support-annotations) Marker Annotation User-defined Annotations 具體描述如下:Built-in Java Annotations(java.lang)
Java內建註解;定義在java.lang中,最常用的三個是@Deprecated、@Overrride、@SuppressWarnings。後續補充了兩個@SafeVarargs和@FunctionsalInterface,後者是在Java 8新增的。具體的介紹如下:
@Deprecated @Deprecated annotation indicates that the marked element is deprecated and should no longer be used. 指示該元素已過時,應該不再被使用。一般在指定時會在註釋中補充為什麼不推薦使用,需要用哪種方式替代。 @Override @Override annotation informs the compiler that the element is meant to override an element declared in a superclass. 告訴編譯器這個方法是過載了父類的方法。在程式碼書寫時,IDE會對方法、引數等進行檢查。 @SuppressWarnings @SuppressWarnings annotation tells the compiler to suppress specific warnings that it would otherwise generate. 告訴編譯器忽略它會產生的警告。 @SafeVarargs –Claims to the compiler that the annotation target does nothing potentially unsafe to its varargs argument. @FunctionalInterface(Java SE 8) –An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.Meta-annotations(java.lang.annotation)
元註解;定義在java.lang.annotation中,定義為用來描述註解的註解(Annotations that apply to other annotations are called meta-annotations. )。常見的四個型別是@Documented、@Inherited、@Retention、@Target。在Java 8新增了兩個:@Repeatable和@Native。具體描述如下:
@Documented
Indicates that annotations with a type are to be documented by javadoc and similar tools by default. 指示該註解是否預設通過Javadoc或者類似的工具進行文件化,是一種語義元註解。通過這種方式定義,並使用了Foo註解,使用Android Studio的Tools->Generate JavaDoc 生成的結果中,對Foo的解釋:
將上述註解定義中@Documented去掉,重新生成JavaDoc,結果是:
@Documented就是控制該註解是否寫進JavaDoc文件中。
使用@Documented並不是把Annotation轉化成JavaDoc註釋語言形如:
而是直接寫入文件。
@Inherited
Indicates that an annotation type is automatically inherited.指示註解型別被自動繼承。如果在解析註解時發現了該欄位,並且在該類中沒有該型別的註解,則對其父類進行查詢。舉個例子,如果一個類中,沒有A註解,但是其父類是有A註解的,並且A註解是被@Inherited註解的(不妨認為保留時態是Runtime),那麼使用反射獲取子類的A註解時,因為獲取不到,所以會去其父類查詢到A註解。使用了@Inherited註解的類,這個註解是可以被用於其子類。
@Retention
Indicates how long annotations with the annotated type are to be retained.
指示該註解的保留時間,即表明註解的生存週期。引數分為以下幾類:
RetentionPolicy.SOURCE The marked annotation is retained only in the source level and is ignored by the compiler. 該註解只保留到程式碼層,編譯器將對其忽略。因此其不會出現在生成的class檔案中。 RetentionPolicy.CLASS The marked annotation is retained by the compiler at compile time, but is ignored by the Java Virtual Machine (JVM). 該註解保留在編譯器中,因此能出現在生成的class檔案中,但是被JVM忽略,所以不能在執行時獲取註解內容。 RetentionPolicy.RUNTIME The marked annotation is retained by the JVM so it can be used by the runtime environment. 該註解能保留在JVM中,可以在執行時通過反射的方法獲取具體內容。 如果自定義註解時不進行指定,預設為RetentionPolicy.CLASS。@Target
Indicates the contexts in which an annotation type is applicable. 指定註解的目標,給什麼型別註解,引數如下: ElementType.ANNOTATION_TYPE –can be applied to an annotation type.給註解註解 ElementType.CONSTRUCTOR –can be applied to a constructor.給構造方法註解 ElementType.FIELD –can be applied to a field or property. 給欄位註解,不要忘了,欄位可以是物件 ElementType.LOCAL_VARIABLE –can be applied to a local variable. 給區域性變數註解 ElementType.METHOD –can be applied to a method-level annotation.給方法註解 ElementType.PACKAGE –can be applied to a package declaration.給包註解 ElementType.PARAMETER –can be applied to the parameters of a method.給引數註解 ElementType.TYPE –can be applied to any element of a class. 給類(型)註解ElementType 常量在 Target 註解中至多隻能出現一次,如下是非法的:@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
@Repetable
The annotation type java.lang.annotation.Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. 指示該註解是否可以多次使用。 在該註解出現之前,如果需要重複使用同樣的註解編譯器會報錯,只能用以下方法通過宣告一個註解陣列來表示,如下:public @interface Authority { String role();} public @interface Authorities { Authority[] value();} public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role="Admin"),@Authority(role="Manager")}) public void doSomeThing(){ }}
缺點是可讀性很差,正違背了註解本身的含義。使用@Repetable後,可以直接對其重複使用,如下:
@Repeatable(Authorities.class)public @interface Authority { String role();} public @interface Authorities { Authority[] value();} public class RepeatAnnotationUseNewVersion { @Authority(role="Admin") @Authority(role="Manager") public void doSomeThing(){ }}
@Native
“Indicates that a field defining a constant value may be referenced from native code.”
用來標記Native的屬性。
Android Support Annotations(android.support:support-annotations)
Android擴充套件註解;android.support:support-annotations提供的一些適用於Android的註解型別,目前更新到22版本。
1、Nullness Annotations 指定引數是否可以為空。 2、資源型別註解 指定引數是何種資源型別:@StringRes, @DrawableRes, @ColorRes, @InterpolatorRes。有種特殊的註解@AnyRes表示可以是任何資源型別: –getResourceName(@AnyRes int resId) –getResources().getResourceName(R.drawable.icon) –getResources().getResourceName(42) 前兩者正確,最後一種方式錯誤。 3、IntDef/StringDef: 型別定義註解 指示是整型/字串型別,其中整型除了可以作為資源的引用之外,也可以用作“列舉”型別使用。 @IntDef和”typedef”作用非常類似,可以用@IntDef指定一個期望的整型常量值列表以修飾API(參考引用見最後)。 4、執行緒註解: @UiThread, @WorkerThread, … 指示執行在哪個執行緒中,分類如下: @UiThread @MainThread @WorkerThread @BinderThread 其中Ui執行緒和主執行緒有本質上的聯絡,主執行緒就是一個Ui執行緒,Ui執行緒也必定是主執行緒。所以很多編譯工具(Android Studio、Lint)可以對其進行互換。使用的不同之處是一般把生命週期相關的內容用@MainThread註解,檢視相關的內容用@UiThread註解。 以AsyncTask為例(參考引用見最後) 因為doInBackgroud方法使用@WorkerThread修飾,而View相關的預設被@UiThread註解,所以在使用myView.setText方法時編譯器會提示這是一個衝突。 5、RGB顏色整型 指定真實的RGB或者ARGB的顏色值。 6、值約束: @Size, @IntRange, @FloatRange 指定值的範圍。 @IntRange指定int和long的範圍,@FloatRange指定float和double的範圍。 @Size的使用方法如下: –集合不能為空: @Size(min=1) –字串最大隻能有23個字元: @Size(max=23) –陣列只能有2個元素: @Size(2) –陣列的大小必須是2的倍數 (例如圖形API中獲取位置的x/y座標陣列: @Size(multiple=2) 7、許可權註解: @RequiresPermission 標註元素的許可權,可以使用anyof和allof進行邏輯上的整合。 8、方法重寫: @CallSuper 標註元素(如過載函式)必須呼叫被過載元素的方法,即必須呼叫super。 9、返回值: @CheckResult Denotes that the annotated method returns a result that it typically is an error to ignore. This is usually used for methods that have no side effect, so calling it without actually looking at the result usually means the developer has misunderstood what the method does. 提醒使用者可能誤會了該元素的使用方法,因為其返回值才是能真正有效提供結果的。忽視返回值可能不能起到使用的效果。如下例子,使用trim函式對String進行處理,需要使用其返回值來真正獲取處理結果,不然是無效的。 10、@VisibleForTesting –可以把這個註解標註到類、方法或者欄位上,以便在測試的時候可以使用。這個Annotation只是一個指示作用,告訴其他開發者該函式為什麼有這麼大的可見程度(為了測試單元或者其他類對其測試使用)。
因此經常用來修飾public和protected,用其修飾private不會報錯,但是意義很小。
它不能改變任何許可權。
Guava has a @VisibleForTesting annotation, but it's only for documentation purpose.
A simple annotation that tells you why a particular property access restriction has been relaxed.A common trick to use in testing is to relax access restrictions to default for a particular property, so that you can use it in a unit test, which resides in the same package (though in different catalog). Whether you thing it's good or bad, remember to give a hint about that to the developer.
該註解用來說明,為什麼該變數或者函式私有訪問許可權被釋放成“公有”或者“package可見”。
通常來說,一個函式預設
這樣定義,則是在package中可訪問,而其他package的類無法訪問,然而
單元測試形如
對此是有訪問許可權的。所以加上 @VisibleForTesting是說明,為什麼你定義其他類似函式需要用private,這裡這個test123函式需要釋放私有訪問許可權呢?哦,原來是需要對測試單元可見(private函式 測試類是訪問不了的)。
類似的,該註解經常和public一起使用,告訴大家為什麼這個函式我現在設計成public的,是為了給其他測試的類使用的。總的來說@VisibleForTesting就是一個標記(Marker)。
以上都是搜到的解釋,所以我寫Demo也進行過大量驗證,@ VisibleForTesting並不能改變許可權,在單元測試以及其他package中,訪問許可權加不加該註解沒有任何改變。
@VisibleForTesting沒有那麼強大,它只是一個很基本的註解。
This annotation is better than nothing in such a case because it at least makes it clear to others using the construct that there is a reason for its otherwise surprisingly relaxed visibility.
11、@Keep –We've also added @Keep to the support annotations. Note however that that annotation hasn't been hooked up to the Gradle plugin yet (though it's in progress.) When finished this will let you annotate methods and classes that should be retained when minimizing the app.Marker Annotation
標記式註解;不指定任何元素,也不需要預設值,主要作用是對一些宣告定義進行標記。
A marker annotation type is an annotation type with no elements, not even one with a default value. Marker Annotations are used to mark a declaration.User-defined Annotations
自定義註解;
- 使用
使用方法很簡單,如下:
值得注意的是,何時可以使用註解,對其有人已經進行過整理,引用如下:
建立類的例項時 –new @Interned MyObject(); 型別轉化時 –myString = (@NonNull String) str; Implements clause –class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... } 拋異常時 –void monitorTemperature() throws @Critical TemperatureException { ... }- 解析
解析主要是對註解進行分析提取,獲取其具體內容的過程,根據其生存週期不同,可以分為兩種解析方式:執行時解析和編譯時解析。
執行時解析
常用 API為: method.getAnnotation(AnnotationName.class); method.getAnnotations(); method.isAnnotationPresent(AnnotationName.class);上述自定義註解解析可以在執行時進行。
編譯時解析
編譯時 Annotation 指 @Retention 為 Class的 Annotation, 自動解析。需要做的 1、自定義類整合自 AbstractProcessor 2、重寫其中的 process 函式關於編譯時解析,主要問題在於自己如何實現apt(Annotation Processor Tool)的processor,編譯環境如何使用它。
1、自定義Annotation;
2、通過apt提供的介面,自定義Annotation processor類,實現主要的處理方法;
3、註冊自定的Processor,以便apt識別;
4、在使用的module內指定使用apt來編譯該processor。
Demo具體實現方法如下:
新建三個module:
分別是:api、compiler、app。
Api和compiler定義為普通Java專案即可,尤其是compiler,最好是Java專案,因為Android專案不提供apt介面的支援,需要新增外掛。
Api:
Java專案:apply plugin: 'java',在此處實現自定義Annotation
Compiler:
Java專案:apply plugin: 'java',在此處實現對Annotation的解析
此處需要讓編譯器知道你自定義了這個Processor,所以需要在此實現services的註冊:
方法一:resources資原始檔夾下新建
META-INF/services/javax.annotation.processing.Processor,並將自定義的processor名稱加入。形如:
內容為
方法二:使用我用紅色框標出來的部分是google提供的庫(@AutoService):
compile 'com.google.auto.service:auto-service:1.0-rc2'
可以通過註解的方式自動新增services註冊。
App:
Android專案:
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
該module是指具體功能的實現部分,在此只需要使用註解即可。
三個模組的gradule分別為:
Api:
Compiler:
解析模組依賴api模組。
App:
這裡我們可以看到該模組不僅依賴api模組,還需要告訴編譯器編譯時需要apt對compiler進行解析(apt procject(‘:compiler’))。
有些人建議另開專案,實現自定義Annotation和apt的Processor,並封裝成jar包放在Android專案中,原理是一樣的。
Build之後,結果如下:這裡在解析自定義apt時我只是讓其列印了使用Annotation的類和方法名稱。
- 作用
總結整理如下:
1、標記,用於告訴編譯器一些資訊 2、編譯時動態處理,如動態生成程式碼 3、執行時動態處理,如得到註解資訊 4、引數/返回值檢查(型別、範圍、許可權等) 5、定義程式實現標準(Callsuper等) 6、描述專案框架的配置資訊(類似XML的作用)- 參考