本文來自尚妝Android團隊路飛
發表於尚妝github博客,歡迎訂閱!java
1、什麼是註解android
一、註解的做用git
二、註解都有哪些github
2、自定義註解api
一、RetentionPolicy.SOURCE數組
二、RetentionPolicy.RUNTIMEapp
三、RetentionPolicy.CLASSjvm
【說在前面的話】maven
要想看懂不少開源庫
,如Arouter, dagger,Butter Knife等,不得不先看懂註解;ide
想更好地提高開發效率和代碼質量,註解能夠幫上很大的忙;
java.lang.annotation,接口 Annotation,在JDK5.0及之後版本引入。
註解是代碼裏的特殊標記,這些標記能夠在編譯、類加載、運行時被讀取,並執行相應的處理。經過使用Annotation,開發人員能夠在不改變原有邏輯的狀況下,在源文件中嵌入一些補充的信息。代碼分析工具、開發工具和部署工具能夠經過這些補充信息進行驗證、處理或者進行部署。
在運行時讀取須要使用Java反射機制進行處理。
Annotation不能運行,它只有成員變量,沒有方法。Annotation跟public、final等修飾符的地位同樣,都是程序元素的一部分,Annotation不能做爲一個程序元素使用。
其實你們都是用過註解的,只是可能沒有過深刻了解它的原理和做用,好比確定見過@Override
,@Deprecated
等。
註解將一些原本重複性的工做,變成程序自動完成,簡化和自動化該過程。好比用於生成Java doc,好比編譯時進行格式檢查,好比自動生成代碼等,用於提高軟件的質量和提升軟件的生產效率。
平時咱們使用的註解有來自JDK裏包含的,也有Android SDK裏包含的,也能夠自定義。
2.一、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
使用收益最大的,仍是須要根據自身需求自定義註解。下面依次介紹三種類型的註解自定義示例:
通常函數的參數值有限定的狀況,好比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); }
運行時註解的定義以下:
// 適用類、接口(包括註解類型)或枚舉 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.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
參考連接:
http://www.jb51.net/article/8...
http://blog.csdn.net/github_3...
http://blog.csdn.net/github_3...
https://www.zhihu.com/questio...
https://github.com/alibaba/AR...
http://blog.csdn.net/asce1885...