本文來自尚妝Android團隊路飛
發表於尚妝github部落格,歡迎訂閱!
- 一、什麼是註解
- 1、註解的作用
- 2、註解都有哪些
- 二、自定義註解
- 1、RetentionPolicy.SOURCE
- 2、RetentionPolicy.RUNTIME
- 3、RetentionPolicy.CLASS
【說在前面的話】
- 要想
看懂很多開源庫
,如Arouter, dagger,Butter Knife等,不得不先看懂註解; - 想更好地提升開發效率和程式碼質量,註解可以幫上很大的忙;
#一、什麼是註解
java.lang.annotation,介面 Annotation,在JDK5.0及以後版本引入。
註解是程式碼裡的特殊標記,這些標記可以在編譯、類載入、執行時被讀取,並執行相應的處理。通過使用Annotation,開發人員可以在不改變原有邏輯的情況下,在原始檔中嵌入一些補充的資訊。程式碼分析工具、開發工具和部署工具可以通過這些補充資訊進行驗證、處理或者進行部署。
在執行時讀取需要使用Java反射機制進行處理。
Annotation不能執行,它只有成員變數,沒有方法。Annotation跟public、final等修飾符的地位一樣,都是程式元素的一部分,Annotation不能作為一個程式元素使用。
其實大家都是用過註解的,只是可能沒有過深入瞭解它的原理和作用,比如肯定見過@Override
,@Deprecated
等。
##1、註解的作用
註解將一些本來重複性的工作,變成程式自動完成,簡化和自動化該過程。比如用於生成Java doc,比如編譯時進行格式檢查,比如自動生成程式碼等,用於提升軟體的質量和提高軟體的生產效率。
##2、註解都有哪些
平時我們使用的註解有來自JDK裡包含的,也有Android SDK裡包含的,也可以自定義。
2.1、JDK定義的元註解
Java提供了四種元註解,專門負責新註解的建立工作,即註解其他註解。
@Target
定義了Annotation所修飾的物件範圍,取值:ElementType.CONSTRUCTOR
:用於描述構造器ElementType.FIELD
:用於描述域ElementType.LOCAL_VARIABLE
:用於描述區域性變數ElementType.METHOD
:用於描述方法ElementType.PACKAGE
:用於描述包ElementType.PARAMETER
:用於描述引數ElementType.TYPE
:用於描述類、介面(包括註解型別) 或enum宣告
@Retention
定義了該Annotation被保留的時間長短,取值:
-RetentionPoicy.SOURCE
:註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄;用於做一些檢查性的操作,比如@Override
和@SuppressWarnings
-RetentionPoicy.CLASS:
註解被保留到class檔案,但jvm載入class檔案時候被遺棄,這是預設的生命週期;用於在編譯時進行一些預處理操作,比如生成一些輔助程式碼(如ButterKnife
)
-RetentionPoicy.RUNTIME
:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在;用於在執行時去動態獲取註解資訊。@Documented
標記註解,用於描述其它型別的註解應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化,不用賦值。@Inherited
標記註解,允許子類繼承父類的註解。 這裡一開始有點理解不了,需要斷句一下,允許子類繼承父類的註解
。示例:
```
Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Sample {
public String name() default "";
}
@Sample
class Test{
}
class Test2 extents Test{
}
這樣類Test2其實也有註解@Sample 。
>如果成員名稱是value,在賦值過程中可以簡寫。如果成員型別為陣列,但是隻賦值一個元素,則也可以簡寫。
>示例以下三個寫法都是等價的。
正常寫法複製程式碼
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
省略value的寫法(只有成員名稱是value時才能省略)複製程式碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
成員型別是陣列,只賦值一個元素的簡寫複製程式碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
** 2.2 JDK內建的其他註解 **
`@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface、@Resources`等。
** 2.3 Android SDK內建的註解 **
Android SDK 內建的註解都在包com.android.support:support-annotations裡,下面以'com.android.support:support-annotations:25.2.0'為例
- 資源引用限制類:用於限制引數必須為對應的資源型別
`@AnimRes @AnyRes @ArrayRes @AttrRes @BoolRes @ColorRes等`
- 執行緒執行限制類:用於限制方法或者類必須在指定的執行緒執行
`@AnyThread @BinderThread @MainThread @UiThread @WorkerThread`
- 引數為空性限制類:用於限制引數是否可以為空
`@NonNull @Nullable`
- 型別範圍限制類:用於限制標註值的值範圍
`@FloatRang @IntRange`
- 型別定義類:用於限制定義的註解的取值集合
`@IntDef @StringDef`
- 其他的功能性註解:
`@CallSuper @CheckResult @ColorInt @Dimension @Keep @Px @RequiresApi @RequiresPermission @RestrictTo @Size @VisibleForTesting`
# 二、自定義註解
使用收益最大的,還是需要根據自身需求自定義註解。下面依次介紹三種型別的註解自定義示例:
## 1、RetentionPolicy.SOURCE
一般函式的引數值有限定的情況,比如View.setVisibility 的引數就有限定,可以看到View.class原始碼裡
除了`IntDef`,還有`StringDef`複製程式碼
@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
## 2、RetentionPolicy.RUNTIME
執行時註解的定義如下:複製程式碼
// 適用類、介面(包括註解型別)或列舉
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.TYPE)
public @interface ClassInfo {
String value();
}
// 適用field屬性,也包括enum常量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
int[] value();
}
// 適用方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodInfo {
String name() default "long";
int age() default 27;
}
定義一個測試類來使用這些註解:複製程式碼
/**
測試執行時註解
*/
@ClassInfo("Test Class")
public class TestRuntimeAnnotation {@FieldInfo(value = {1, 2})
public String fieldInfo = "FiledInfo";@MethodInfo(name = "BlueBird")
public static String getMethodInfo() {return return fieldInfo; 複製程式碼
}
}使用註解:複製程式碼
/**
測試執行時註解
*/
private void _testRuntimeAnnotation() {
StringBuffer sb = new StringBuffer();
Class<?> cls = TestRuntimeAnnotation.class;
Constructor<?>[] constructors = cls.getConstructors();
// 獲取指定型別的註解
sb.append("Class註解:").append("\n");
ClassInfo classInfo = cls.getAnnotation(ClassInfo.class);
if (classInfo != null) {sb.append(cls.getSimpleName()).append("\n"); sb.append("註解值: ").append(classInfo.value()).append("\n\n"); 複製程式碼
}
sb.append("Field註解:").append("\n");
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class); if (fieldInfo != null) { sb.append(field.getName()).append("\n"); sb.append("註解值: ").append(Arrays.toString(fieldInfo.value())).append("\n\n"); } 複製程式碼
}
sb.append("Method註解:").append("\n");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); if (methodInfo != null) { sb.append(Modifier.toString(method.getModifiers())).append(" ") .append(method.getName()).append("\n"); sb.append("註解值: ").append("\n"); sb.append("name: ").append(methodInfo.name()).append("\n"); sb.append("age: ").append(methodInfo.age()).append("\n"); } 複製程式碼
}
System.out.print(sb.toString());
}
```
所做的操作都是通過反射獲取對應元素,再獲取元素上面的註解,最後得到註解的屬性值。因為涉及到反射,所以執行時註解的效率多少會受到影響,現在很多的開源專案使用的是編譯時註解。
3、RetentionPolicy.CLASS
3.1 新增依賴
如果Gradle 外掛是2.2以上的話,不需要新增以下
android-apt
依賴。
classpath 'com.android.tools.build:gradle:2.2.1'複製程式碼
在整個工程的 build.gradle 中新增android-apt
的依賴
buildscript {
repositories {
jcenter()
mavenCentral() // add
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' //2.2以上無需新增apt依賴
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // add
}
}複製程式碼
android-apt
:
android-apt 是一個Gradle外掛,協助Android Studio 處理annotation processors, 它有兩個目的:
- 允許配置只在編譯時作為註解處理器的依賴,而不新增到最後的APK或library
- 設定源路徑,使註解處理器生成的程式碼能被Android Studio正確的引用
伴隨著 Android Gradle 外掛 2.2 版本的釋出,近期 android-apt 作者在官網發表宣告證實了後續將不會繼續維護 android-apt,並推薦大家使用 Android 官方外掛提供的相同能力。也就是說,大約三年前推出的 android-apt 即將告別開發者,退出歷史舞臺,Android Gradle 外掛提供了名為 annotationProcessor 的功能來完全代替 android-apt。
3.2 定義要使用的註解
建一個Java庫來專門放註解,庫名為:annotations
定義註解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value();
}複製程式碼
3.3 定義註解處理器
另外建一個Java庫工程,庫名為:processors
這裡必須為Java庫,不然會找不到javax包下的相關資源
build.gradle 要依賴以下:
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
compile(project(':annotations'))複製程式碼
其中,
auto-service 自動用於在 META-INF/services 目錄資料夾下建立 javax.annotation.processing.Processor 檔案;
javapoet用於產生 .java 原始檔的輔助庫,它可以很方便地幫助我們生成需要的.java 原始檔
示例:
package com.example;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// Filer是個介面,支援通過註解處理器建立新檔案
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement element : annotations) {
//新建檔案
if (element.getQualifiedName().toString().equals(MyAnnotation.class.getCanonicalName())) {
// 建立main方法
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
// 建立HelloWorld類
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
try {
// 生成 com.example.HelloWorld.java
JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
.addFileComment(" This codes are generated automatically. Do not modify!")
.build();
// 生成檔案
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//在Gradle console 列印日誌
for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
System.out.println("------------------------------");
// 判斷元素的型別為Class
if (element.getKind() == ElementKind.CLASS) {
// 顯示轉換元素型別
TypeElement typeElement = (TypeElement) element;
// 輸出元素名稱
System.out.println(typeElement.getSimpleName());
// 輸出註解屬性值System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());
}
System.out.println("------------------------------");
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(MyAnnotation.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}複製程式碼
3.4 在程式碼中使用定義的註解 :
需要依賴上面的兩個java庫annotations和processors
import com.example.MyAnnotation;
@MyAnnotation("test")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}複製程式碼
編譯後就會生成指定包名的指定檔案,如圖:
3.5 註解處理器的輔助介面
在自定義註解處理器的初始化介面,可以獲取到以下4個輔助介面:
public class MyProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
}複製程式碼
Types
: Types是一個用來處理TypeMirror的工具Elements
: Elements是一個用來處理Element的工具Filer
: 一般我們會用它配合JavaPoet來生成我們需要的.java檔案Messager
: Messager提供給註解處理器一個報告錯誤、警告以及提示資訊的途徑
3.5 帶有註解的庫提供給第三方
以下例子預設用gradle外掛2.2以上,不再使用apt
一般使用編譯時註解的庫,都會有三個module:
- 定義註解的module , java庫,xxxx-annotations
- 實現註解器的module, java庫,xxxx-compiler
- 提供對外介面的module, android庫,xxxx-api
module xxxx-api的依賴這麼寫:
dependencies { annotationProcessor 'xxxx-compiler:1.0.0' compile ' xxxx-annotations:1.0.0' //....others }
然後第三方使用時,可以像如下這樣依賴:
dependencies {
...
compile 'com.google.dagger:dagger:2.9'
annotationProcessor 'com.google.dagger:dagger-compiler:2.9'
}複製程式碼
sample project見AnnotationSample
參考連結:
www.jb51.net/article/802…
blog.csdn.net/github_3518…
blog.csdn.net/github_3518…
www.zhihu.com/question/36…
github.com/alibaba/ARo…
blog.csdn.net/asce1885/ar…