自定義注解之編譯時(shí)注解(RetentionPolicy.CLASS)(一)

http://blog.csdn.net/github_35180164/article/details/52121038

說(shuō)到編譯時(shí)注解(RetentionPolicy.CLASS)都要和注解處理器(Annotation Processor) 扯上關(guān)系,因?yàn)檫@里是真正體現(xiàn)編譯時(shí)注解價(jià)值的地方。需要注意的一點(diǎn)是,運(yùn)行時(shí)注解(RetentionPolicy.RUNTIME)源碼注解(RetentionPolicy.SOURCE)也可以在注解處理器進(jìn)行處理,不同的注解有各自的生命周期,根據(jù)你實(shí)際使用來(lái)確定。
注解處理器(Annotation Processor)
首先來(lái)了解下什么是注解處理器,注解處理器是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解(Annotation)。你可以自定義注解,并注冊(cè)到相應(yīng)的注解處理器,由注解處理器來(lái)處理你的注解。一個(gè)注解的注解處理器,以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成文件(通常是.java文件)作為輸出。這些生成的Java代碼是在生成的.java文件中,所以你不能修改已經(jīng)存在的Java類,例如向已有的類中添加方法。這些生成的Java文件,會(huì)同其他普通的手動(dòng)編寫(xiě)的Java源代碼一樣被javac編譯。

自定義注解(RetentionPolicy.CLASS)
先來(lái)定義要使用的注解,這里建一個(gè)Java庫(kù)來(lái)專門放注解,庫(kù)名為:annotations,和下面要?jiǎng)?chuàng)建的注解處理器分開(kāi),至于為什么要分開(kāi)創(chuàng)建后面再說(shuō)。注解庫(kù)指定JDK版本為1.7,如何指定往下看。自定義注解如下:

/** 
 * 編譯時(shí)注解 
 */  
@Retention(RetentionPolicy.CLASS)  
@Target(ElementType.TYPE)  
public @interface MyAnnotation {  
    String value();  
}  

定義的是編譯時(shí)注解,對(duì)象為類或接口等。

定義注解處理器****
下面來(lái)定義注解處理器,另外建一個(gè)Java庫(kù)工程,庫(kù)名為:processors,記得是和存放注解的庫(kù)分開(kāi)的。注意,這里必須為Java庫(kù),不然會(huì)找不到javax包下的相關(guān)資源。來(lái)看下現(xiàn)在的目錄結(jié)構(gòu):


這里定義一個(gè)注解處理器 MyProcessor,每一個(gè)處理器都是繼承于AbstractProcessor,并要求必須復(fù)寫(xiě) process() 方法,通常我們使用會(huì)去復(fù)寫(xiě)以下4個(gè)方法:

/** 
 * 每一個(gè)注解處理器類都必須有一個(gè)空的構(gòu)造函數(shù),默認(rèn)不寫(xiě)就行; 
 */  
public class MyProcessor extends AbstractProcessor {  
  
    /** 
     * init()方法會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。 
     * ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer 
     * @param processingEnv 提供給 processor 用來(lái)訪問(wèn)工具框架的環(huán)境 
     */  
    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);  
    }  
  
    /** 
     * 這相當(dāng)于每個(gè)處理器的主函數(shù)main(),你在這里寫(xiě)你的掃描、評(píng)估和處理注解的代碼,以及生成Java文件。 
     * 輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素 
     * @param annotations   請(qǐng)求處理的注解類型 
     * @param roundEnv  有關(guān)當(dāng)前和以前的信息環(huán)境 
     * @return  如果返回 true,則這些注解已聲明并且不要求后續(xù) Processor 處理它們; 
     *          如果返回 false,則這些注解未聲明并且可能要求后續(xù) Processor 處理它們 
     */  
    @Override  
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
        return false;  
    }  
  
    /** 
     * 這里必須指定,這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的。注意,它的返回值是一個(gè)字符串的集合,包含本處理器想要處理的注解類型的合法全稱 
     * @return  注解器所支持的注解類型集合,如果沒(méi)有這樣的類型,則返回一個(gè)空集合 
     */  
    @Override  
    public Set<String> getSupportedAnnotationTypes() {  
        Set<String> annotataions = new LinkedHashSet<String>();  
        annotataions.add(MyAnnotation.class.getCanonicalName());  
        return annotataions;  
    }  
  
    /** 
     * 指定使用的Java版本,通常這里返回SourceVersion.latestSupported(),默認(rèn)返回SourceVersion.RELEASE_6 
     * @return  使用的Java版本 
     */  
    @Override  
    public SourceVersion getSupportedSourceVersion() {  
        return SourceVersion.latestSupported();  
    }  
}  

上面注釋說(shuō)的挺清楚了,我們需要處理的工作在 process()**** 方法中進(jìn)行,等下給出例子。對(duì)于 getSupportedAnnotationTypes() 方法標(biāo)明了這個(gè)注解處理器要處理哪些注解,返回的是一個(gè)Set 值,說(shuō)明一個(gè)注解處理器可以處理多個(gè)注解。除了在這個(gè)方法中指定要處理的注解外,還可以通過(guò)注解的方式來(lái)指定(SourceVersion也一樣):

@SupportedSourceVersion(SourceVersion.RELEASE_8)  
@SupportedAnnotationTypes("com.example.annotation.cls.MyAnnotation")  
public class MyProcessor extends AbstractProcessor {  
    // ...  
}  

因?yàn)榧嫒莸脑?,特別是針對(duì)Android平臺(tái),建議使用重載** getSupportedAnnotationTypes()** 和getSupportedSourceVersion()方法代替@SupportedAnnotationTypes@SupportedSourceVersion
現(xiàn)在來(lái)添加對(duì)注解的處理,簡(jiǎn)單的輸出一些信息即可,代碼如下:

@Override  
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    // roundEnv.getElementsAnnotatedWith()返回使用給定注解類型的元素  
    for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {  
        System.out.println("------------------------------");  
        // 判斷元素的類型為Class  
        if (element.getKind() == ElementKind.CLASS) {  
            // 顯示轉(zhuǎn)換元素類型  
            TypeElement typeElement = (TypeElement) element;  
            // 輸出元素名稱  
            System.out.println(typeElement.getSimpleName());  
            // 輸出注解屬性值  
            System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());  
        }  
        System.out.println("------------------------------");  
    }  
    return false;  
}  

到這里注解處理器也寫(xiě)好了,下面就看怎么運(yùn)行它了。

運(yùn)行注解處理器
在運(yùn)行前,你需要在主項(xiàng)目工程中引入 annotations 和 **processors **這兩個(gè)庫(kù)(引入 **processors **庫(kù)不是個(gè)好做法,后面介紹更適當(dāng)?shù)姆椒ǎ?。這時(shí)如果你直接編譯或者運(yùn)行工程的話,是看不到任何輸出信息的,這里還要做的一步操作是指定注解處理器的所在,需要做如下操作:
1、在 **processors **庫(kù)的 **main **目錄下新建 **resources **資源文件夾;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾;
3、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件寫(xiě)入注解處理器的全稱,包括包路徑;
來(lái)看下整個(gè)目錄結(jié)構(gòu):


處理完就可以使用了,我們?cè)陧?xiàng)目中使用 @MyAnnotation 注解:

@MyAnnotation("Hello Annotation")  
public class MainActivity extends AppCompatActivity {  
    // ...  
}  

到這里我們重新編譯下工程就應(yīng)該有輸出了,如果沒(méi)看到輸出則先清理下工程在編譯,如下兩個(gè)操作:


輸出信息如下:

現(xiàn)在注解處理器已經(jīng)可以正常工作了~
當(dāng)然了,上面還遺留著一個(gè)問(wèn)題,我們的主項(xiàng)目中引用了 processors 庫(kù),但注解處理器只在編譯處理期間需要用到,編譯處理完后就沒(méi)有實(shí)際作用了,而主項(xiàng)目添加了這個(gè)庫(kù)會(huì)引入很多不必要的文件,為了處理這個(gè)問(wèn)題我們需要引入個(gè)插件android-apt,它能很好地處理這個(gè)問(wèn)題。
在介紹這個(gè)插件前,我想先介紹個(gè)好用的庫(kù)AutoService,這里有個(gè)坑。

AutoService
前面在指定注解處理器的時(shí)候你會(huì)不會(huì)覺(jué)得很麻煩?那么多步驟就為添加一個(gè)注解處理器,不過(guò)沒(méi)關(guān)系,AutoService 可以幫你解決這個(gè)問(wèn)題。
AutoService注解處理器是Google開(kāi)發(fā)的,用來(lái)生成 **META-INF/services/javax.annotation.processing.Processor **文件的,你只需要在你定義的注解處理器上添加 @AutoService(Processor.class) 就可以了,簡(jiǎn)直不能再方便了。
先給 **processors **庫(kù)依賴上 AutoService,你可以直接在 AndroidStudio 工具上搜索添加,如下:


添加好以后就可以直接用了,在我們之前定義的注解處理器上使用:

apply plugin: 'com.android.application'  
  
android {  
    // ...  
    packagingOptions {  
        exclude 'META-INF/services/javax.annotation.processing.Processor'  
    }  
}  

這樣就不會(huì)報(bào)錯(cuò)了,這是其中的一個(gè)解決方法,還有個(gè)更好的解決方法就是用上上面提到的android-apt了,下面正式登場(chǎng)

Android-apt
那么什么是android-apt呢?官網(wǎng)有這么一段描述:
The android-apt plugin assists in working with annotation processors in combination with Android Studio. It has two purposes:
1、Allow to configure a compile time only annotation processor as a dependency, not including the artifact in the final APK or library2、Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio
大體來(lái)講它有兩個(gè)作用:能在編譯時(shí)期去依賴注解處理器并進(jìn)行工作,但在生成 APK 時(shí)不會(huì)包含任何遺留的東西
能夠輔助 Android Studio 在項(xiàng)目的對(duì)應(yīng)目錄中存放注解處理器在編譯期間生成的文件

這個(gè)就可以很好地解決上面我們遇到的問(wèn)題了,來(lái)看下怎么用。
首先在整個(gè)工程的 **build.gradle **中添加如下兩段語(yǔ)句:

buildscript {  
    repositories {  
        jcenter()  
        mavenCentral()  // add  
    }  
    dependencies {  
        classpath 'com.android.tools.build:gradle:2.1.2'  
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add  
    }  
}  

在主項(xiàng)目(app)的 build.gradle 中也添加兩段語(yǔ)句:

apply plugin: 'com.android.application'  
apply plugin: 'com.neenbedankt.android-apt' // add  
// ...  
dependencies {  
    compile fileTree(include: ['*.jar'], dir: 'libs')  
    testCompile 'junit:junit:4.12'  
    compile 'com.android.support:appcompat-v7:23.4.0'  
    compile project(':annotations')  
//    compile project(':processors')  替換為下面  
    apt project(':processors')  
}  

上面提到android-apt的作用有對(duì)編譯時(shí)期生成的文件處理,關(guān)于生成文件的功能就不得不提 JavaPoet

但是對(duì)于AndroidStudio 2.2使用 Java 8 功能和 Jack 工具鏈的問(wèn)題

如果你安裝官網(wǎng)設(shè)置Java 8 功能和 Jack 工具鏈的配置后遇到 Error:Could not get unknown property 'classpath' for task ':app:transformJackWithJackForInstantrunconfigDebug' of type com.Android.build.gradle.internal.pipeline.TransformTask.

那么檢查你是否使用了帶有apt moudle
解決方法

刪除項(xiàng)目build.gradle文件里的
apply plugin: 'android-apt'
把a(bǔ)pt替換成annotationProcessor
刪除根目錄的build.gradleclasspath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

這個(gè)不用Java8也可以這么寫(xiě)的,現(xiàn)在AS自帶了注解插件

只需要 
dependencies {
    //apt project(':processors')
    annotationProcessor project(':processors')
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評(píng)論 25 709
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來(lái) What:A...
    zlcook閱讀 29,730評(píng)論 15 116
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 我好像有很多多余的時(shí)光,當(dāng)我不用上課,當(dāng)我沒(méi)有作業(yè),當(dāng)我不用上班,當(dāng)我不用完成別人交給的任務(wù)……等等,當(dāng)只剩我一個(gè)...
    學(xué)著跟自己相處閱讀 261評(píng)論 0 1
  • 2017.10.15 下午要送女兒去學(xué)畫(huà)畫(huà),出門前,把車鑰匙拿出來(lái),拿在手上。出門,關(guān)門。瞥見(jiàn)門口有一袋垃圾,順手...
    中和西閱讀 306評(píng)論 0 0

友情鏈接更多精彩內(nèi)容