Android 自定義注解之編譯時注解(RetenttionPolicy.CLASS)

注解處理器(Annotation Processor)

注解處理器是javac的一個工具萤彩,它用來在編譯時掃描和處理注解(Annotation)。你可以自定義注解,并注冊到相應(yīng)的注解處理器,由注解處理器來處理你的注解慨菱。一個注解的注解處理器,以Java代碼(或者編譯過的字節(jié)碼)作為輸入戴甩,生成文件(通常是.java文件)作為輸出符喝。這些生成的Java代碼是在生成的.java文件中,所以你不能修改已經(jīng)存在的Java類甜孤,例如向已有的類中添加方法洲劣。這些生成的Java文件,會同其他普通的手動編寫的Java源代碼一樣被javac編譯课蔬。

RetentionPolicy.CLASS:注解被保留到class文件囱稽,但jvm加載class文件時候被遺棄,這是默認(rèn)的生命周期二跋;

1. 新建兩個Module

File->New->New Module...->Java Library战惊,填寫好Library name和Java class name后點(diǎn)擊完成。
注意扎即,這里必須為Java庫吞获,不然會找不到j(luò)avax包下的相關(guān)資源。


圖1.png

其中annations是存放注解的谚鄙,processors是存放注解處理器的各拷。

2. 新建編譯時注解

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

3. 定義注解處理器

定義一個注解處理器 CustomProcessor ,每一個處理器都是繼承于AbstractProcessor闷营,并要求必須復(fù)寫 process() 方法烤黍,通常我們使用復(fù)寫以下4個方法:

/**
 * 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù),默認(rèn)不寫就行
 */
public class CustomProcessor extends AbstractProcessor{
    /**
     * init()方法會被注解處理器工具調(diào)用傻盟,并輸入ProcessingEnvironment參數(shù)速蕊。
     * ProcessingEnvironment 提供很多有用的工具類Elements,Types和Filter
     * @param processingEnvironment 提供給process用來訪問工具框架的環(huán)境
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    /**
     * 相當(dāng)于每個處理器的主函數(shù)main()娘赴,在這里寫掃描规哲、評估和處理注解的代碼,以及生成java文件。
     * 輸入?yún)?shù)RoundEnvironment可以查詢出包含特定注解的被注解元素
     * @param set 請求處理注解類型
     * @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境
     * @return 返回true,則這些注解已聲明并且不要求后續(xù)Processor處理他們;
     *          返回false做祝,則這些注解未聲明并且可能要求后續(xù)Processor處理他們;
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }

    /**
     * 這里必須指定谅将,這個注解處理器是注冊給那個注解的。
     * 注意:它的返回值是一個字符串的集合,包含本處理器想要處理注解的注解類型的合法全程。
     * @return 注解器所支持的注解類型集合,如果沒有這樣的類型痘番,則返回一個空集合。
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(CustomAnnation.class.getCanonicalName());
        return annotations;
    }

    /**
     * 指定Java版本,通常這里使用SourceVersion.latestSupported(),
     * 默認(rèn)返回SourceVersion.RELEASE_6
     * @return 使用的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

也可以使用注解的方式來指定Java版本和注解類型


圖2.png

4. 添加注解的處理

    /**
     * 相當(dāng)于每個處理器的主函數(shù)main()汞舱,在這里寫掃描伍纫、評估和處理注解的代碼,以及生成java文件昂芜。
     * 輸入?yún)?shù)RoundEnvironment可以查詢出包含特定注解的被注解元素
     * @param set 請求處理注解類型
     * @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境
     * @return 返回true莹规,則這些注解已聲明并且不要求后續(xù)Processor處理他們;
     *          返回false,則這些注解未聲明并且可能要求后續(xù)Processor處理他們;
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)返回使用給定的注解類型的元素
        for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.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(CustomAnnotation.class).value());
            }
            System.out.println("----------------------------------");
        }
        return false;
    }

5. 注解處理器配置

1泌神、在 processors 庫的 main 目錄下新建 resources 資源文件夾良漱;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾欢际;
3母市、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件;
4损趋、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱患久,包括包路徑;

項(xiàng)目結(jié)構(gòu):


圖3.png

6. 使用

主Module依賴annotations和processors兩個moudle浑槽,然后編譯蒋失,如果未打印出結(jié)果,則Build->ReBuild或者Build->Clean Project->Make Project.

@CustomAnnotation("Hello World")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

7. 打印結(jié)果

圖4.png

8. 存在的問題

我們的主項(xiàng)目中引用了 processors 庫桐玻,但注解處理器只在編譯處理期間需要用到篙挽,編譯處理完后就沒有實(shí)際作用了,而主項(xiàng)目添加了這個庫會引入很多不必要的文件镊靴,為了處理這個問題我們需要引入個插件android-apt铣卡,它能很好地處理這個問題。

9. AutoService

AutoService注解處理器是Google開發(fā)的邑闲,用來生成 META-INF/services/javax.annotation.processing.Processor 文件的算行,你只需要在你定義的注解處理器上添加 @AutoService(Processor.class) 就可以了,簡直不能再方便了苫耸。

  • 依賴AutoService庫,在processors的build.gradle文件中添加一下依賴儡陨,并同步工程褪子。
    compile 'com.google.auto.service:auto-service:1.0-rc2'
  • 使用


    圖5.png

10. 問題

這時重新Make下工程也能看到同樣的輸出信息了。但是如果你編譯生成APK時骗村,可能會出現(xiàn)文件重復(fù)的問題嫌褪。解決辦法是在主項(xiàng)目的 build.gradle 加上這么一段:

  packagingOptions {  
     exclude 'META-INF/services/javax.annotation.processing.Processor'  
  }  

這樣就不會報錯了,這是其中的一個解決方法胚股,還有個更好的解決方法就是用上面提到的android-apt

11. 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 library
2、Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio

大體來講它有兩個作用:
能在編譯時期去依賴注解處理器并進(jìn)行工作,但在生成 APK 時不會包含任何遺留的東西
能夠輔助 Android Studio 在項(xiàng)目的對應(yīng)目錄中存放注解處理器在編譯期間生成的文件

12. 使用

// 主工程的build.gradle
buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:1.3.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
// 主Module的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'


dependencies {
// ...
    compile project(':annotations')
    apt project(':processors')
//    compile project(':processors')
//    compile project(':processors1')
}

13. JavaPoet

在使用編譯時注解時缨伊,需要在編譯期間對注解進(jìn)行處理摘刑,在這里我們沒辦法影響程序的運(yùn)行邏輯,但我們可以進(jìn)行一些需處理刻坊,比如生成一些功能性代碼來輔助程序的開發(fā)枷恕,最常見的是生成.java 源文件,并在程序中可以調(diào)用到生成的文件谭胚。這樣我們就可以用注解來幫助我們處理一些固定邏輯的重復(fù)性代碼(如butterknife)徐块,提高開發(fā)的效率。

通過注解處理器來生成 .java 源文件基本上都會使用javapoet 這個庫灾而,JavaPoet一個是用于產(chǎn)生 .java 源文件的輔助庫胡控,它可以很方便地幫助我們生成需要的.java 源文件,下面來看下具體使用方法旁趟。
使用如下代碼進(jìn)行依賴:

    compile "com.squareup:javapoet:1.9.0"

14. 注解生成器

/**
 * 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù)铜犬,默認(rèn)不寫就行
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.mazaiting.CustomAnnotation")
public class CustomProcessor extends AbstractProcessor{

    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // Filter是個接口,至此吃通過注解處理器創(chuàng)建新文件
        mFiler = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
            // 判斷元素的類型為Class
            if (element.getKind() == ElementKind.CLASS) {
                // 顯示轉(zhuǎn)換元素類型
                TypeElement typeElement = (TypeElement) element;
                // 輸出元素名稱
//                System.out.println(typeElement.getSimpleName());
//                // 輸出注解屬性值
//                System.out.println(typeElement.getAnnotation(CustomAnnotation.class).value());
                // 創(chuàng)建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();
                String value = element.getAnnotation(CustomAnnotation.class).value();
                String first = value.substring(0, 1);
                String className = first.toUpperCase() + value.substring(1, value.length());
                TypeSpec valueClass =
                        TypeSpec.classBuilder(className)
                                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                                .addMethod(main)
                                .build();
                // 生成文件
                try {
                    // 生成com.mazaiting.xxx.java
                    JavaFile javaFile =
                            JavaFile.builder("com.mazaiting.example", valueClass)
                                    .addFileComment("This codes are generated automatically. Do not modify!")
                                    .build();
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
}

15. MainActivity使用

@CustomAnnotation("HelloWorld")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

16. 生成Java類

生成的Java類結(jié)構(gòu)


圖6.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轻庆,一起剝皮案震驚了整個濱河市癣猾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌余爆,老刑警劉巖纷宇,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛾方,居然都是意外死亡像捶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門桩砰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拓春,“玉大人,你說我怎么就攤上這事亚隅∨鹈В” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵煮纵,是天一觀的道長懂鸵。 經(jīng)常有香客問我,道長行疏,這世上最難降的妖魔是什么匆光? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮酿联,結(jié)果婚禮上终息,老公的妹妹穿的比我還像新娘夺巩。我一直安慰自己,他們只是感情好周崭,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布柳譬。 她就那樣靜靜地躺著,像睡著了一般休傍。 火紅的嫁衣襯著肌膚如雪征绎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天磨取,我揣著相機(jī)與錄音人柿,去河邊找鬼。 笑死忙厌,一個胖子當(dāng)著我的面吹牛凫岖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逢净,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼哥放,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了爹土?” 一聲冷哼從身側(cè)響起甥雕,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胀茵,沒想到半個月后社露,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琼娘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年峭弟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脱拼。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞒瘸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熄浓,到底是詐尸還是另有隱情情臭,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布玉组,位于F島的核電站谎柄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惯雳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一鸿摇、第九天 我趴在偏房一處隱蔽的房頂上張望石景。 院中可真熱鬧,春花似錦、人聲如沸潮孽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽往史。三九已至仗颈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椎例,已是汗流浹背挨决。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留订歪,地道東北人脖祈。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像刷晋,于是被迫代替她去往敵國和親盖高。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361