一埃篓、什么是注解察净?
注解對于開發(fā)人員來講既熟悉又陌生,熟悉是因為只要你是做開發(fā)锅必,都會用到注解(常見的@Override)事格;陌生是因為即使不使用注解也照常能夠進行開發(fā);注解不是必須的搞隐,但了解注解有助于我們深入理解某些第三方框架(比如Android Support Annotations分蓖、ButterKnife、xUtils尔许、ActiveAndroid等)么鹤,提高工作效率。
Java注解又稱為標注味廊,是Java從1.5開始支持加入源碼的特殊語法元數(shù)據(jù)蒸甜;Java中的類、方法余佛、變量柠新、參數(shù)、包都可以被注解辉巡。這里提到的元數(shù)據(jù)是描述數(shù)據(jù)的數(shù)據(jù)恨憎,結(jié)合實例來說明:
<string name="app_name">AnnotationDemo</string>
這里的"app_name"就是描述數(shù)據(jù)"AnnotionDemo"的數(shù)據(jù),這是在配置文件中寫的,注解是在源碼中寫的憔恳,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_layout);
new Thread(new Runnable(){
@Override
public void run(){
setTextInOtherThread();
}
}).start();
}
在上面的代碼中瓤荔,在MainActivity.java中復寫了父類Activity.java的onCreate方法,使用到了@Override注解钥组。但即使不加上@Override注解標記代碼输硝,程序也能夠正常運行。那這里的@Override注解有什么用呢程梦?使用它有什么好處点把?事實上,@Override是告訴編譯器這個方法是一個重寫方法屿附,如果父類中不存在該方法郎逃,編譯器會報錯,提示該方法不是父類中的方法挺份。如果不小心拼寫錯誤褒翰,將onCreate寫成了onCreat,而且沒有使用@Override注解压恒,程序依然能夠編譯通過影暴,但運行結(jié)果和期望的大不相同。而如果使用了@Override注解探赫,拼寫錯誤則會得到提示型宙。從示例可以看出,注解有助于閱讀代碼伦吠。
使用注解很簡單妆兑,根據(jù)注解類的@Target所修飾的對象范圍,可以在類毛仪、方法搁嗓、變量、參數(shù)箱靴、包中使用“@+注解類名+[屬性值]”的方式使用注解腺逛。比如:
@UiThread
private void setTextInOtherThread(@StringRes int resId){
TextView threadTxtView = (TextView)MainActivity.this.findViewById(R.id.threadTxtViewId);
threadTxtView.setText(resId);
}
特別說明:
- 注解僅僅是元數(shù)據(jù),和業(yè)務邏輯無關衡怀,所以當你查看注解類時棍矛,發(fā)現(xiàn)里面沒有任何邏輯處理;
- javadoc中的@author抛杨、@version够委、@param、@return怖现、@deprecated茁帽、@hide、@throws、@exception潘拨、@see是文檔注釋標記吊输,并不是注解;
二战秋、注解的作用
格式檢查:告訴編譯器信息璧亚,比如被@Override標記的方法如果不是父類的某個方法讨韭,IDE會報錯脂信;
減少配置:運行時動態(tài)處理,得到注解信息透硝,實現(xiàn)代替配置文件的功能狰闪;
減少重復工作:比如第三方框架xUtils,通過注解@ViewInject減少對findViewById的調(diào)用濒生,類似的還有(ButterKnife埋泵、ActiveAndroid等);
三罪治、注解是如何工作的丽声?
注解僅僅是元數(shù)據(jù),和業(yè)務邏輯無關觉义,所以當你查看注解類時雁社,發(fā)現(xiàn)里面沒有任何邏輯處理,例如XUtils的ViewInject
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
/* parent view id */
int parentId() default 0;
}
如果注解不包含業(yè)務邏輯處理晒骇,必然有人來實現(xiàn)這些邏輯霉撵。注解的邏輯實現(xiàn)是元數(shù)據(jù)的用戶來處理的,注解僅僅提供它定義的屬性(類/方法/變量/參數(shù)/包)的信息洪囤,注解的用戶來讀取這些信息并實現(xiàn)必要的邏輯徒坡。當使用java中的注解時(比如@Override、@Deprecated瘤缩、@SuppressWarnings)JVM就是用戶喇完,它在字節(jié)碼層面工作。如果是自定義的注解剥啤,比如第三方框架ActiveAndroid锦溪,它的用戶是每個使用注解的類,所有使用注解的類都需要繼承Model.java铐殃,在Model.java的構(gòu)造方法中通過反射來獲取注解類中的每個屬性
四海洼、注解和配置文件的區(qū)別
通過上面的描述可以發(fā)現(xiàn),其實注解干的很多事情富腊,通過配置文件也可以干坏逢,比如為類設置配置屬性;但注解和配置文件是有很多區(qū)別的,在實際編程過程中是整,注解和配置文件配合使用在工作效率肖揣、低耦合、可拓展性方面才會達到權(quán)衡浮入。
4.1 龙优、配置文件:
使用場合:
外部依賴的配置,比如build.gradle中的依賴配置事秀;
同一項目團隊內(nèi)部達成一致的時候彤断;
非代碼類的資源文件(比如圖片、布局易迹、數(shù)據(jù)宰衙、簽名文件等);
優(yōu)點:
降低耦合睹欲,配置集中供炼,容易擴展,比如Android應用多語言支持窘疮;
對象之間的關系一目了然袋哼,比如strings.xml;
xml配置文件比注解功能齊全闸衫,支持的類型更多涛贯,比如drawable、style等楚堤;
缺點:
繁瑣疫蔓;
類型不安全,比如R.java中的都是資源ID身冬,用TextView的setText方法時傳入int值時無法檢測出該值是否為資源ID衅胀,但@StringRes可以;
4.2酥筝、注解:
使用場合:
動態(tài)配置信息滚躯;
代為實現(xiàn)程序邏輯(比如xUtils中的@ViewInject代為實現(xiàn)findViewById);
代碼格式檢查嘿歌,比如Override掸掏、Deprecated、NonNull宙帝、StringRes等丧凤,便于IDE能夠檢查出代碼錯誤;
優(yōu)點:
在class文件中步脓,提高程序的內(nèi)聚性愿待;
減少重復工作浩螺,提高開發(fā)效率,比如findViewById仍侥。
缺點:
如果對annotation進行修改要出,需要重新編譯整個工程;
業(yè)務類之間的關系不如XML配置那樣一目了然农渊;
程序中過多的annotation患蹂,對于代碼的簡潔度有一定影響;
擴展性較差砸紊;
五传于、常用注解庫
- ButterKnife
- Dagger2
- Retrofit
- EventBus
- Afinal
開源的Android的orm和ioc應用開發(fā)框架,其特點是小巧靈活批糟,代碼入侵量少格了。在android應用開發(fā)中看铆,通過Afinal的ioc框架徽鼎,諸如ui綁定,事件綁定弹惦,通過注解可以自動綁定否淤。通過Afinal的orm框架,無需任何配置信息棠隐,一行代碼就可以對android的sqlite數(shù)據(jù)庫進行增刪改查操作石抡。同時,Afinal內(nèi)嵌了finalHttp等簡單易用的工具助泽,可以輕松的對http就行求情的操作啰扛。
六、Annotation 分類
6.1嗡贺、 標準 Annotation
包括 Override, Deprecated, SuppressWarnings隐解,標準 Annotation 是指 Java 自帶的幾個 Annotation,上面三個分別表示重寫函數(shù)诫睬,不鼓勵使用(有更好方式煞茫、使用有風險或已不在維護),忽略某項 Warning
6.2摄凡、元 Annotation
@Retention, @Target, @Inherited, @Documented续徽,元 Annotation 是指用來定義 Annotation 的 Annotation
6.2.1、@Retention(英文:保留)
用于指定被修飾的Annotation可以保留多長時間亲澡,只能修飾Annotation定義钦扭。
@Retention包含一個RetentionPolicy類型的value成員變量,使用@Retention必須為該value成員變量指定值床绪。value成員變量的值有3個選擇:
- RetentionPolicy.CLASS: 編譯器將把Annotation記錄在class文件中客情。當運行java程序時捎琐,JVM不可獲取Annotation信息。(默認值)
- RetentionPolicy.RUNTIME: 編譯器將把Annotation記錄在class文件中裹匙。當運行java程序時瑞凑,JVM也可獲取Annotation信息,程序可以通過反射獲取該Annotation信息
- RetentionPolicy.SOURCE: Annotation只保留在源代碼中(.java文件中)概页,編譯器直接丟棄這種Annotation籽御。
比如:
//定義下面的MyAnnotaion保留到運行時,也可以使用value=RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaion{}
6.2.2惰匙、@Target (目標)
用于指定被修飾的Annotation能用于修飾哪些程序單元技掏,只能修飾Annotation定義。它包含一個名為value的成員變量项鬼,取值如下:
- @Target(ElementType.ANNOTATION_TYPE): 指定該該策略的Annotation只能修飾Annotation.
- @Target(ElementType.TYPE) : 接口哑梳、類豪娜、枚舉袱贮、注解
- @Target(ElementType.FIELD) : 成員變量(字段、枚舉的常量)
- @Target(ElementType.METHOD) : 方法
- @Target(ElementType.PARAMETER): 方法參數(shù)
- @Target(ElementType.CONSTRUCTOR): 構(gòu)造函數(shù)
- @Target(ElementType.LOCAL_VARIABLE): 局部變量
- @Target(ElementType.PACKAGE): 修飾包定義
- @Target(ElementType.TYPE_PARAMETER): java8新增膘怕,可以使用在方法參數(shù)上
- @Target(ElementType.TYPE_USE): java8新增龄毡,修飾的注解稱為Type Annotation(類型注解)吠卷,Type Annotation可用在任何用到類型的地方。
比如:
@Target(ElementType.FIELD)
public @interface MyActionListener{}
6.2.3沦零、@Documented
用于指定被修飾的Annotation將被javadoc工具提取成文檔祭隔。即說明該注解將被包含在javadoc中。
6.2.4路操、@Inherited
用于指定被修飾的Annotation具有繼承性疾渴。即子類可以繼承父類中的該注解。比如:注解@TestAnnotation被元注解@Inherited修飾屯仗,把@TestAnnotation添加在類Base上搞坝,則Base的所有子類也將默認使用@TestAnnotation注解。
6.2.5祭钉、Repeatable(可重復)
Java SE8引入的注解瞄沙,表示這個注解可以在同一處多次聲明
6.3、 自定義 Annotation
自定義 Annotation 表示自己根據(jù)需要定義的 Annotation慌核,定義時需要用到上面的元 Annotation
這里只是一種分類而已距境,也可以根據(jù)作用域分為源碼時、編譯時垮卓、運行時 Annotation
七垫桂、如何自定義注解
首先,我們需要先了解注解處理器Processor粟按,注解處理器有什么作用呢诬滩?首先它會在編譯期被調(diào)用霹粥,可以掃描特定注解的信息,你可以為你自己的的注解注冊處理器疼鸟,一個特定的注解處理器以java源碼作為輸入后控,然后生成一些文件作(通常為java)為輸出,這些java文件同樣會被編譯空镜。這意味著浩淘,你可以根據(jù)注解的信息和被注解類的信息生成你想生成的代碼!
需求:
定義一個注解MyAnnotation吴攒,去注解MainActivity张抄,然后處理器掃描生成一個java文件,這個java文件有個輸出Hello MyAnnotation的方法洼怔,運行的我們的MainAcitivity署惯,然后調(diào)用這個java文件的方法。
7.1镣隶、創(chuàng)建注解工程
同樣我們先創(chuàng)建一個Java工程极谊,編寫一個注解類MyAnnotation
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value() default "MyAnnotation";
}
7.2、創(chuàng)建Android工程
定義好我們注解的MyAnnotation矾缓,接下來怀酷,我們要用這個去注解MainActivity,現(xiàn)在我們是在Java工程嗜闻,那么我們新創(chuàng)建一個Android工程,里面有個MainActivity桅锄,這個工程依賴我們MyAnnotation所在的工程琉雳。
@MyAnnotation
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
接下來我們就要通過自己定義的注解處理器去掃描這個注解進而生成java文件,但是在此之前友瘤,我們需要先了解注解處理的工作流程和相關API翠肘。
7.3、創(chuàng)建Compiler工程
AbstractProcessor
AbstractProcessor就是系統(tǒng)抽象出來的處理器類辫秧,如果我們要處理自己定義的注解束倍,就必須借助于它。
例如:
public class MyProcessor extends AbstractProcessor{
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
我們需要重寫的方法有:
init(ProcessingEnvironment processingEnv) : 所有的注解處理器類都必須有一個無參構(gòu)造函數(shù)盟戏。然而绪妹,有一個特殊的方法init(),它會被注解處理工具調(diào)用柿究,以ProcessingEnvironment作為參數(shù)邮旷。ProcessingEnvironment 提供了一些實用的工具類Elements, Types和Filer。我們在后面將會使用到它們蝇摸。
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : 這類似于每個處理器的main()方法婶肩。你可以在這個方法里面編碼實現(xiàn)掃描办陷,處理注解,生成 java 文件律歼。使用RoundEnvironment 參數(shù)民镜,你可以查詢被特定注解標注的元素。
getSupportedAnnotationTypes(): 在這個方法里面你必須指定哪些注解應該被注解處理器注冊险毁。注意殃恒,它的返回值是一個String集合,包含了你的注解處理器想要處理的注解類型的全稱辱揭。換句話說离唐,你在這里定義你的注解處理器要處理哪些注解。
getSupportedSourceVersion() : 用來指定你使用的 java 版本问窃,建議使用SourceVersion.latestSupported()亥鬓。
7.4、注冊處理器
我們在編譯好的META-INF/services添加我們的處理器路徑域庇,谷歌已經(jīng)提供一個很方便的庫嵌戈,幫助我們做這些東西,我們只需要在處理器工程添加依賴
compile 'com.google.auto.service:auto-service:1.0-rc2'
然后在Myprocessor中添加@AutoService(Processor.class)的注解听皿,這樣就完成了我們處理器的注冊熟呛。
編譯成生成的META-INF/services中就注冊了我們的MyProcessor
接下來,我們編寫一個我們自己的處理器尉姨,生成java文件,來講解一下相關API庵朝,以及要注意的事項。
/**
* 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù)又厉,默認不寫就行;
*/
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
//處理Element的工具類
private Elements mElementUtils;
//生成文件的工具
private Filer mFiler;
//日志信息的輸出
private Messager mMessager;
/**
* 這相當于每個處理器的主函數(shù)main()九府,你在這里寫你的掃描、評估和處理注解的代碼覆致,以及生成Java文件侄旬。
* 輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素
* @param annotations 請求處理的注解類型
* @param roundEnvironment 有關當前和以前的信息環(huán)境
* @return 如果返回 true煌妈,則這些注解已聲明并且不要求后續(xù) Processor 處理它們儡羔;
* 如果返回 false,則這些注解未聲明并且可能要求后續(xù) Processor 處理它們
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<? extends Element> set = roundEnvironment.getElementsAnnotatedWith(MyAnnotation.class);
for (Element element : set){
if(element.getKind() == ElementKind.CLASS){
TypeElement typeElement = (TypeElement) element;
brewJavaFile(typeElement);
}
}
return true;
}
/**
* init()方法會被注解處理工具調(diào)用璧诵,并輸入ProcessingEnviroment參數(shù)汰蜘。
* ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer
* @param processingEnvironment 提供給 processor 用來訪問工具框架的環(huán)境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
mElementUtils = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
}
/**
* 這里必須指定,這個注解處理器是注冊給哪個注解的腮猖。注意鉴扫,它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱
* @return 注解器所支持的注解類型集合澈缺,如果沒有這樣的類型坪创,則返回一個空集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(MyAnnotation.class.getCanonicalName());
return set;
}
/**
* 指定使用的Java版本炕婶,通常這里返回SourceVersion.latestSupported(),默認返回SourceVersion.RELEASE_6
* @return 使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private void brewJavaFile(TypeElement pElement){
//sayHello 方法
MyAnnotation myAnnotation = pElement.getAnnotation(MyAnnotation.class);
MethodSpec methodSpec = MethodSpec.methodBuilder("sayHello")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).returns(void.class)
.addStatement("$T.out.println($S)",System.class,"Hello"+myAnnotation.value()).build();
// class
TypeSpec typeSpec = TypeSpec.classBuilder(pElement.getSimpleName().toString()+"$$HelloWorld").addModifiers(Modifier.PUBLIC,Modifier.FINAL).addMethod(methodSpec).build();
// 獲取包路徑莱预,把我們的生成的源碼放置在與被注解類中同一個包路徑中
JavaFile javaFile = JavaFile.builder(mElementUtils.getPackageOf(pElement).getQualifiedName().toString(),typeSpec).build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
}
7.5柠掂、測試
新建一個Android工程,該工程依賴注解工程依沮,至于compiler處理器工程涯贞,我們要使用apt的方式依賴。
這里有人要問了危喉,apt是什么宋渔?
它主要有兩個作用:
- 能在編譯時期去依賴注解處理器并進行工作,但在生成 APK 時不會包含任何遺留的東西
- 能夠輔助 Android Studio 在項目的對應目錄中存放注解處理器在編譯期間生成的文件
了解完apt,那我們就先在項目目錄下的build.gradle中添加
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'這個依賴
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.1'
// apt
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
然后在Android 工程中辜限,添加這個插件依賴
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
然后就可以使用apt依賴處理器工程了
apt project(':xs_compiler')
運行我們的Android工程皇拣,查看build生成文件
順利生成我們的文件了,剩下就是怎么去調(diào)用這個sayHello的方法薄嫡,我們的思路是通過反射生成的類氧急,調(diào)用該方法。
在注解工程中毫深,新建AnnotationApi類吩坝,編碼如下
public class MyAnnotationApi {
public static void sayHelloAnnotation(Object pTarget){
String name = pTarget.getClass().getCanonicalName();
try {
Class clazz = Class.forName(name+"$$HelloWorld");
Method method = clazz.getMethod("sayHello");
method.invoke(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
然后在MainActivity中調(diào)用sayHelloAnnotation的方法
@MyAnnotation
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
MyAnnotationApi.sayHelloAnnotation(this);
}
}
查看輸出:
I/System.out: HelloMyAnnotation