APT動態(tài)生成代碼的實際應用場景

APT

Annotation Processing Tool 注解處理器度液。 APT編譯時期就會掃描標識有某一些注解的源代碼,并對這些源代碼和注解做一些額外的操作,例如獲取注解的屬性信息镜粤,獲取標識該注解的源代碼類或類成員的一些信息等操作幢踏。

作用時期

編譯階段

我們可以利用編譯時期髓需,通過 APT 掃描到這些注解和源代碼并生成一些額外的源文件。

應用場景

我們都知道微信支付和微信登錄都需要在我們的包名下面新建一個 package.wxapi 這樣的一個包房蝉,并在該包下創(chuàng)建對應的微信入口類僚匆,例如 WXEntryActivityWXPayActivity 這兩個類,那么我就感覺很反感需要在我們自己的包目錄下去新建這么兩個類搭幻,那么能不能通過注解處理器的方式咧擂,在編譯時期就幫我們將這件事給完成呢?答案是可以檀蹋,下面我們就來探討如何去實現(xiàn)松申。

  • 常規(guī)的做法是這樣的:
微信入口文件
  • 最終我們希望的結(jié)果是這樣的
代碼自動生成的結(jié)果

開發(fā)需求

需求:新建一個類 WXDelegateEntryActivity(在根包下)繼承至 AppCompatActivity 并對這個類標識自定義注解 WXEntryAnnotation。在編譯時期,APT在處理這個注解和WXDelegateEntryActivity時就自動生成一些源文件贸桶,例如(WXEntryActivity 或者 WXPayActivity)舅逸。

開發(fā)步驟

我們將整個工程分為以下幾個小模塊

劃分模塊
  • app

編譯之后會生成 app/build/generated/source/apt/debug/包名.wxapi/WXEntryActivity.java

  • lib-annotaion

存放注解的模塊。注意:該 module 是 java library 類型皇筛,并不是 Android library 類型哦琉历。

注解類
  • lib-compiler

負責掃描注解,并生成注解的模塊水醋。注意:該 module 是 java library 類型旗笔,并不是 Android library 類型哦。

整個 demo 就由這三個小模塊組成拄踪。

依賴關(guān)系

他們之間的依賴關(guān)系如下:

依賴關(guān)系圖
  • app 依賴 lib-annotation 注解模塊和注解處理器模塊
app依賴
  • lib-compiler 依賴 lib-annotation 模塊
lib-compiler依賴

編寫注解

  • 編寫注解的作用是什么蝇恶?

APT 在編譯時期就能找到標識該注解的源代碼。例如:在編譯時惶桐, APT 找到 WXEntryAnnotation 注解的源代碼 WXDelegateEntryActivity 撮弧。

package com.example.annotation;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface WXEntryAnnotation {

    /**
     * 標識我們WXEntrayActivity所在的包名
     *
     * 最后會生成 packageName().wxapi.WXEntryActivity 這么一個類。
     * @return 返回包名
     */
    String packageName();


    /**
     * 表示 WXEntrayActivity 需要繼承的那個類的字節(jié)碼文件耀盗,例如我們需要將生成的 WXEntrayActivity 去繼承 WXDelegateEntryActivity 那么這個 superClass 返回的就是 WXDelegateEntryActivity 的字節(jié)碼文件對象想虎。
     * @return
     */
    Class superClass();

}

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}
//注意版本需要和 lib-compiler 的一致
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

編寫注解處理器

上面已經(jīng)提過,注解處理器就是用來掃描注解叛拷,并處理注解的工具舌厨。對應的模塊就是 lib-compiler 模塊。它在編譯時會掃描注解 WXEntryAnnotation 注解忿薇。下面在看看如何去定義一個注解處理器裙椭。

    1. 定義一個類,繼承 AbstractProcessor 這個抽象類

process(...) 就是用于處理注解的方法署浩。

public class WXProcessor extends AbstractProcessor{
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}
    1. 如何讓編譯器知道有這么一個注解處理器呢揉燃?

這里介紹一個最簡單的方式:使用 Google 提供的一個 AutoService 注解來實現(xiàn)即可。

compile 'com.google.auto.service:auto-service:1.0-rc2'

//在 processor 類上引用即可
@AutoService(Processor.class)
public class WXProcessor extends AbstractProcessor {
    1. 告訴注解處理器需要處理哪些注解筋栋?

覆寫 getSupportedAnnotationTypes() 返回該處理器需要處理的注解類型即可炊汤。

@Override
public Set<String> getSupportedAnnotationTypes() {
    final Set<String> supportAnnotationTypes = new HashSet<>();
    final Set<Class<? extends Annotation>> supportAnnotations = getSupportAnnotations();
    for (Class<? extends Annotation> supportAnnotion : supportAnnotations) {
        supportAnnotationTypes.add(supportAnnotion.getCanonicalName());
    }
    return supportAnnotationTypes;
}
/**
 * 設(shè)置需要掃描的注解
 *
 * @return
 */
private final Set<Class<? extends Annotation>> getSupportAnnotations() {
    Set<Class<? extends Annotation>> supportAnnotations = new HashSet<>();
    supportAnnotations.add(WXEntryAnnotation.class);
    return supportAnnotations;
}
    1. 額外的配置
@AutoService(Processor.class)
//表示源碼版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class WXProcessor extends AbstractProcessor {}

build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation project(':lib-annotation')
    compile 'com.google.auto.service:auto-service:1.0-rc2'
}
//指定源碼版本號,需要和 WXProcessor 的源碼一致哦弊攘。
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

了解幾個常用的 API

Element 表示一個元素抢腐,它有幾個實現(xiàn)類

  • VariableElement

代表成員變量元素

  • ExecutableElement

代表類中的方法元素

  • TypeElement

代表類元素

  • PackageElement

代表包元素

在 app 模塊使用注解

@WXEntryAnnotation(packageName = "com.example", superClass = DelegateEntryActivity.class)
public class DelegateEntryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

處理掃描出來的 Element

我們在定義 WXEntryAnnotation 注解時就指明了注解是只能使用在類或接口上,因此使用該 WXEntryAnnotation 的代碼就是使用 TypeElement 來描述的襟交。

/**
 * @param set              the annotation types requested to be processed
 * @param roundEnvironment environment for information about the current and prior round
 * @return
 */
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    if (set != null && !set.isEmpty()) {
        
        WXEntryAnnotationVisitor visitor = new WXEntryAnnotationVisitor(processingEnv.getFiler());
        for (TypeElement typeElement : set) {
            
            //在這里我們已經(jīng)明確的知道WXEntryAnnotation只能用于type類型迈倍,因此可以使用ElementFilter.typesIn將其轉(zhuǎn)化為具體的TypeElement類型的集合。
            Set<TypeElement> typeElements = ElementFilter.typesIn(elementsAnnotatedWith);
            
            //遍歷使用該注解的類捣域,方法啼染,屬性
            for (TypeElement element : typeElements) {
                  
                
                List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
                for (AnnotationMirror annotationMirror : annotationMirrors) {
                   
                    
                    //判斷當前處理的注解就是掃描出來的注解
                    if (annotationMirror.getAnnotationType().asElement().getSimpleName().toString().equals(typeElement.getSimpleName().toString())) {
                        //獲取注解的值
                        Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();
                        
                        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
                            
                            AnnotationValue value = entry.getValue();
                                              
                            value.accept(visitor, null);
                        }
                        
                    }
                }
            }
        }
        return true;
    }
    return false;
}

下面的操作是對 process 方法的每一步的解釋(具體可以下源碼查看):

  • 獲取使用了自定義注解的元素集合
//set就是 process 方法的參數(shù)
for (TypeElement typeElement : set) {
    //使用該注解的元素集合
    Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);
    
    //測試輸出
System.out.println(elementsAnnotatedWith);
    //[com.example.DelegateEntryActivity]
}
  • 轉(zhuǎn)化Element集合為具體的TypeElemnt類型集合

在這里我們已經(jīng)明確的知道WXEntryAnnotation只能用于type類型宴合,因此可以使用ElementFilter.typesIn將其轉(zhuǎn)化為具體的TypeElement類型的集合。DelegateActivity 使用了 WXEntryAnnotation 注解

Set<TypeElement> typeElements = ElementFilter.typesIn(elementsAnnotatedWith);
  • 遍歷取出使用 WXEntryAnnotation 注解的元素
for (TypeElement element : typeElements) {
    //element表示使用了WXEntryAnnotation的元素
}
  • 取出當前元素的注解集合

當前元素可能不止使用了 WXEntryAnnotation 這一個注解迹鹅,例如還使用 @Deprecated 那么就需要對其進行過濾卦洽。

List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
  • 過濾出 WXEntryAnnotation 這個注解信息
for (AnnotationMirror annotationMirror : annotationMirrors) {
    if (annotationMirror.getAnnotationType().asElement().getSimpleName().toString().equals(typeElement.getSimpleName().toString())) {
        ...
    }
}
  • 取出WXEntryAnnotation注解信息

以下方法是獲取一個注解的信息,在 Map 中的泛型可以看到

ExecutableElement : 表示注解方法徒欣,例如 packageName()或者superClass()

AnnotationValue : 表示注解的值逐样,這個就是我們需要的東西了。

//獲取注解的值
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();
  • 取出注解中AnnotationValue的對應的值

因為 WXEntryAnnotation 有個方法是 superClass 返回的是 Class 對象打肝,那么這個字節(jié)碼是無法通過 AnnotationValue 直接獲取的,那么這節(jié)介紹使用 AnnotationValueVisitor注解訪問器來獲取對應的值挪捕。

//注解訪問器
WXEntryAnnotationVisitor visitor = new WXEntryAnnotationVisitor(processingEnv.getFiler());

//獲取注解的值
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();

for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {

    AnnotationValue value = entry.getValue();

    value.accept(visitor, null);
}

AnnotationVisitor 注解訪問器

在上一步的操作中粗梭,最終會將表示注解值的 AnnotationValue 對象交給 AnnotationVisitor 去訪問。那么下面來了解一下這個類的基本使用级零。

  • WXEntryAnnotationVisitor 繼承 SimpleAnnotationValueVisitor7(版本要對應)
public class WXEntryAnnotationVisitor extends SimpleAnnotationValueVisitor7<Void, Void> {
    
}
  • 訪問字符串類型的回調(diào)

覆寫 visitString 即可断医,當訪問到 packageName 這個屬性時,那么該方法就會被回調(diào)奏纪。

@Override
public Void visitString(String s, Void aVoid) {
    this.packageName = s;
    
    if (typeMirror != null && packageName != null) {
        //生成微信源代碼
        generateWXEntryCode();
    }
    return super.visitString(s, aVoid);
}
  • 訪問 Class 類型的回調(diào)

在 WXEntryAnnotation 中有一個屬性是 superClass 鉴嗤,當 visitor 訪問到這種屬性時就會回調(diào) visitType 并且將其以 TypeMirror 的方式返回。

@Override
public Void visitType(TypeMirror typeMirror, Void p) {

    this.typeMirror = typeMirror;
    if (typeMirror != null && packageName != null) {
        //生成微信源代碼
        generateWXEntryCode();
    }
    return p;
}

--

基于上面兩步序调,已經(jīng)可以拿到對應的 packageName 和 typeMirror 對象了醉锅,那么接下來工作就是根據(jù) packageName 和 typeMirror 去創(chuàng)建對應的微信入口文件了。

生成微信入口源文件

現(xiàn)在我們來探討一下如何去生成 WXEntryActivity 发绢?

目前 GITHUB 中有一個專門用于代碼生成的開源框架javapoet

使用

  • 在 lib-compiler 中引入該庫
compile 'com.squareup:javapoet:1.9.0'
  • 編寫生成代碼
/**
 * WXEntryActivity代碼生成
 */
private final void generateWXEntryCode() {
    TypeSpec targetActivityTypeSpec =
    TypeSpec.classBuilder("WXEntryActivity")
                            .addModifiers(Modifier.FINAL)
                            .addModifiers(Modifier.PUBLIC)
                            .superclass(TypeName.get(this.typeMirror))
                            .build();
            final JavaFile javaFile =
                    JavaFile.builder(this.packageName + ".wxapi", targetActivityTypeSpec)
                            .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
      }
}

對于這個庫如何使用硬耍,可以去官網(wǎng)查看。

源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末边酒,一起剝皮案震驚了整個濱河市经柴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墩朦,老刑警劉巖坯认,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異氓涣,居然都是意外死亡牛哺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門春哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荆隘,“玉大人,你說我怎么就攤上這事赴背∫埽” “怎么了晶渠?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長燃观。 經(jīng)常有香客問我褒脯,道長,這世上最難降的妖魔是什么缆毁? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任番川,我火速辦了婚禮,結(jié)果婚禮上脊框,老公的妹妹穿的比我還像新娘颁督。我一直安慰自己,他們只是感情好浇雹,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布沉御。 她就那樣靜靜地躺著,像睡著了一般昭灵。 火紅的嫁衣襯著肌膚如雪吠裆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天烂完,我揣著相機與錄音试疙,去河邊找鬼。 笑死抠蚣,一個胖子當著我的面吹牛祝旷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柱徙,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼缓屠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了护侮?” 一聲冷哼從身側(cè)響起敌完,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羊初,沒想到半個月后滨溉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了气笙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡脯颜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贩据,到底是詐尸還是另有隱情栋操,我是刑警寧澤闸餐,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站矾芙,受9級特大地震影響舍沙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剔宪,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一拂铡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧葱绒,春花似錦感帅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骚秦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間璧微,已是汗流浹背作箍。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留前硫,地道東北人胞得。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像屹电,于是被迫代替她去往敵國和親阶剑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理危号,服務(wù)發(fā)現(xiàn)牧愁,斷路器,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,734評論 25 707
  • 我 還是很喜歡你外莲,可是我們怎么慢慢走遠了猪半,變得陌生,變得無言偷线,越來越?jīng)]話說磨确,或許我們都只是對方生命中的過客,但那些...
    戲天大大閱讀 275評論 0 1
  • 001認真對待小承諾 其實声邦,我沒想過學生會不信任老師乏奥,因為對于班上每個孩子,我像個大家長一樣操心著他們亥曹。但有時候?qū)?..
    OQ熊閱讀 110評論 0 0