Java編譯時(shí)注解處理器的學(xué)習(xí)總結(jié)

APT的簡介

定義

APT即是Annotation Processing Tool 捶惜,它是一個(gè)javac的一個(gè)工具,中文的意思是編譯時(shí)注解處理器,可以用來在編譯時(shí)掃描和處理注解胞谭。通過APT可以取到注解和被注解對象的相關(guān)信息,在拿到這些信息后我們可以根據(jù)需求來自動(dòng)生成一些代碼男杈,省去了手動(dòng)編寫丈屹。獲取注解及生成代碼都是在代碼編譯的時(shí)候完成的,相比反射在運(yùn)行時(shí)處理注解大大提高了程序性能伶棒。APT的核心就是AbstractProcessor類旺垒。

如何在Android中使用APT呢

在Android工程中使用APT,至少需要兩個(gè)Java Library模塊組成苞冯,在Android中創(chuàng)建Java Library的的步驟是袖牙,首先建一個(gè)Android項(xiàng)目,然后點(diǎn)擊File-> New ->New Module,打開如圖所示舅锄,然后選擇Java Library模塊鞭达。


image.png

這兩個(gè)模塊的作用是:
一個(gè)Annotation模塊司忱,這個(gè)用來存在自定義的注解。
一個(gè)Compiler模塊畴蹭,這個(gè)模塊依賴Annotation模塊坦仍。
在項(xiàng)目的App模塊和其它的業(yè)務(wù)模塊都需要依賴Annotation模塊,同時(shí)需要通過annotationProcessor依賴Compiler模塊叨襟。
app模塊的gradle中依賴關(guān)系如下:

implementation project(':annotation')
annotationProcessor project(':factory-compiler')

實(shí)現(xiàn)ButterKnife例子學(xué)習(xí)APT

步驟:

新建一個(gè)Android的app的工程繁扎。
創(chuàng)建一個(gè)Java Library ,定義要被處理的注解(apt-annotation)。
創(chuàng)建一個(gè)Java Library,定義注解處理器生成具體的類糊闽。(apt-processor)
創(chuàng)建一個(gè)Android library module梳玫,通過反射調(diào)用apt-processor模塊生成的方法,實(shí)現(xiàn)View的綁定右犹。

工程目錄如下:


image.png
在apt-annotation中定義一個(gè)注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default -1;
}

因?yàn)檫@個(gè)注解是在成員變量中使用的提澎,保留的時(shí)間是在.class字節(jié)碼文件,不需要值JVM期間保留念链,這也體現(xiàn)了APT只是在編譯器的編譯時(shí)期完成工作盼忌。

在apt-processor中的gradle文件中加入兩個(gè)依賴。

為了解決android studio升級到3.4.2,gradle升級到5.1.1后,apt不會(huì)執(zhí)行,沒辦法自動(dòng)生成注解文件的問題掂墓,還需要添加 annotationProcessor谦纱。

  implementation project(':apt-annotation')
  implementation 'com.google.auto.service:auto-service:1.0-rc4'
 annotationProcessor  'com.google.auto.service:auto-service:1.0-rc4'

第二個(gè)依賴是AutoService,它是google開發(fā)的一個(gè)庫君编,在使用@AutoService注解時(shí)需要用到跨嘉,它的作用是用來生成META.INF/services/javax.annotation.processing.Processor文件的,在使用注解器的時(shí)候就不需要手動(dòng)添加該文件啦粹。

在apt-processor中創(chuàng)建注解處理器

該Module是Java Library偿荷,不能是Android Library窘游,因?yàn)锳ndroid平臺(tái)的是基于OpenJDK的唠椭,而OpenJDK中是不包含APT的相關(guān)代碼。

BindViewProcessor要引用AutoService的注解忍饰,以及繼承AbstractProcessor.并重寫相應(yīng)的方法:

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }
    
}

接下來對重寫的方法進(jìn)行解析

  • public synchronized void init()
    這個(gè) 方法是用來初始化處理器的贪嫂,方法中有個(gè)ProcessingEnvironment processingEnvironment類型的參數(shù),它是一個(gè)注解處理工具的集合艾蓝。包含了眾多的工具類力崇,例如Filer可以用來編寫新文件。Meessage可以用來處理錯(cuò)誤信息赢织。Elements是一個(gè)可以處理Element的工具類亮靴。
  • 什么是Elments?
    在Java語言中,Elenent是一個(gè)接口于置,表示一個(gè)程序的元素茧吊,例如包,類,方法搓侄,變量瞄桨。Element已知的子接口有如下幾種。
    PackageElement 表示一個(gè)包程序元素讶踪。
    ExecutableElement表示某個(gè)類或者接口的方法芯侥,構(gòu)造方法和初始化程序,包括注釋類型元素乳讥。
    TypeElement 表示一個(gè)類或者接口的元素柱查。注意對有關(guān)類型及其成員的信息的訪問。注意云石,枚舉類型是一種類物赶,而注解類型是一種接口。
    VariableElement表示一個(gè)字段留晚,enum常量酵紫,方法或者構(gòu)成方法參數(shù),局部變量或者異常參數(shù)错维。
  • public boolean process()
    這個(gè)是AbstractProcessor中最重要的一個(gè)方法奖地,該方法的返回值是一個(gè)boolean類型,返回值表示注解是否由當(dāng)前Processor處理赋焕,如果返回true,則這些注解由這個(gè)處理器進(jìn)行處理参歹,后續(xù)的其他Processor無需處理它們。如果返回false隆判,則這些注解未在次Processor中處理犬庇,需要后續(xù)的其他Processor處理它們。在這個(gè)方法中,我們需要檢驗(yàn)被注解的對象是否合法,可以編寫處理注解的代碼麸锉,以及自動(dòng)生成需要的java文件等月洛。處理的大部分邏輯都在這個(gè)類中。
  • public Set<String> getSupportedAnnotationTypes()
    這方法是返回一個(gè)set集合,集合中指定那些注解是需要這個(gè)處理器處理的。
  • public SourceVersion getSupportedSourceVersion()
    這個(gè)返回是返回當(dāng)前的正在使用的java版本。通常返回SourceVersion.latestSupported()就可以了纽帖。

接下來是編寫B(tài)ingViewProcessor的代碼,注意改文件中不可以含有中文举反,否則編譯不通過懊直。代碼中的中文是為了方便理解加的解析,在運(yùn)行的時(shí)候要?jiǎng)h除火鼻。如果需要中文解析的可以在build.gradle中添加

//中文亂碼問題(錯(cuò)誤:編碼GBK不可映射字符)
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
    private Elements mElementUtil;
    private Map<String,ClassCreatorFactory> mClassCreatorFactoryMap=new HashMap<>();
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //初始化Element
        mElementUtil= processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mClassCreatorFactoryMap.clear();
        //獲取所有包含了@BingView注解的集合
        Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element:elements){
            VariableElement variableElement= (VariableElement) element;
            TypeElement typeElement= (TypeElement) variableElement.getEnclosingElement();
            String fullClassName=typeElement.getQualifiedName().toString();
            ClassCreatorFactory classCreatorFactory=mClassCreatorFactoryMap.get(fullClassName);
            if (classCreatorFactory==null){
                classCreatorFactory=new ClassCreatorFactory( mElementUtil,typeElement);
                mClassCreatorFactoryMap.put(fullClassName,classCreatorFactory);
            }
            BindView bindViewAnnotation=variableElement.getAnnotation(BindView.class);
            int id=bindViewAnnotation.value();
            classCreatorFactory.putElement(id,variableElement);
}
   //開始創(chuàng)建Java類
            for (String key:mClassCreatorFactoryMap.keySet()){
                ClassCreatorFactory factory=mClassCreatorFactoryMap.get(key);
                try {
                    JavaFileObject fileObject=processingEnv.getFiler().createSourceFile(factory.getClassFullName(),factory.getTypeElement());
                    Writer writer=fileObject.openWriter();
                    writer.write(factory.generateJavaCode());
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        //返回true說明這個(gè)processor要處理這個(gè)注解室囊,后續(xù)的Processor不需
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //設(shè)置這個(gè)注解器是給哪個(gè)注解類用的瘦陈,這里是給BingView的注解類使用
        HashSet<String> supportType=new LinkedHashSet<>();
        supportType.add(BindView.class.getCanonicalName());
        return supportType;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        //返回java的版本
        return SourceVersion.latestSupported();
    }

}

ClassCreatorFactory的代碼如下,注意該類的代碼不能出現(xiàn)中文的注釋波俄,該類的代碼就是相當(dāng)于在代碼中通過字符串的方式編寫代碼晨逝,需要注意空格等細(xì)節(jié)問題,這個(gè)類很容易出錯(cuò)懦铺,可以利用JavaPoet根據(jù)實(shí)體類生成捉貌。

public class ClassCreatorFactory {
    private String mClassName;
    private String mPackageName;
    private TypeElement typeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorFactory(Elements elements, TypeElement typeElement) {
        //獲取該類
        this.typeElement = typeElement;
        //獲取包元素
         PackageElement packageElement = elements.getPackageOf(typeElement);
        mPackageName = packageElement.getQualifiedName().toString();
        String temClassName = typeElement.getSimpleName().toString();
        //生成類的名稱
        mClassName = temClassName + "_ViewBinding";
    }

    /**
     * 添加VariableElement,例如字段,局部變量冬念,參數(shù)等
     * @param id
     * @param element
     */
    public void putElement(int id,VariableElement element){
        mVariableElementMap.put(id,element);
    }

    /**
     * 創(chuàng)建java代碼趁窃,可以看出是文本的形式,對字符進(jìn)行拼接即可急前。
     * @return
     */
    public String generateJavaCode(){
        StringBuilder stringBuilder=new StringBuilder();
        //注釋
        stringBuilder.append("/**\n"+"* 通過APT自動(dòng)創(chuàng)建的類"+"\n*/\n");
        //包名
        stringBuilder.append("package ").append(mPackageName).append(";\n\n");
        stringBuilder.append("public class ").append(mClassName).append("{\n");
        //創(chuàng)建方法
        generateMethod(stringBuilder);
        stringBuilder.append("\n}\n");
        return stringBuilder.toString();
    }

    /**
     * 創(chuàng)建方法
     * @param stringBuilder
     */
    private void generateMethod(StringBuilder stringBuilder) {
        stringBuilder.append("\tpublic void bindView(");
        stringBuilder.append(typeElement.getQualifiedName());
        stringBuilder.append(" value) { \n");
        for (int id:mVariableElementMap.keySet()){
            VariableElement variableElement=mVariableElementMap.get(id);
            String viewName=variableElement.getSimpleName().toString();
            String viewType=variableElement.asType().toString();
            stringBuilder.append("\t\tvalue.");
            stringBuilder.append(viewName);
            stringBuilder.append(" = ");
            stringBuilder.append("(");
            stringBuilder.append(viewType);
            //findViewById(id);
            stringBuilder.append(")(((android.app.Activity)value).findViewById( ");
            stringBuilder.append(id+" ));");
            stringBuilder.append("\n}\n");
        }

    }
    public String getClassFullName(){
        return mPackageName+"."+mClassName;
    }
    public TypeElement getTypeElement(){
        return typeElement;
    }


}

創(chuàng)建apt-sdk

通過反射調(diào)用生成類的方法醒陆。

public class BindViewUtil {
    public static void bindView(Activity activity) throws ClassNotFoundException, NoSuchMethodException {
        try {
            Class cla=activity.getClass();
            Class bindViewClass=Class.forName(cla.getName()+"_ViewBinding");
            Method bindViewMethod=bindViewClass.getMethod("bindView",cla);
            bindViewMethod.setAccessible(true);
            bindViewMethod.invoke(bindViewClass.newInstance(),activity);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}
在App中調(diào)用

需要加入以下的權(quán)限:

   implementation project(":apt-annotation")
   //要用 annotationProcessor ,否則編譯不通過
   annotationProcessor project(":apt-procrssor")
   implementation project(':apt-sdk')

使用該Apt

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.textView)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            BindViewUtil.bindView(this);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        textView.setText("你好,注解處理器");
    }
}

接下來我們看到生成的類在下面的目錄中


image.png
public class MainActivity_ViewBinding{
    public void bindView(com.example.hx.apttest.MainActivity value) { 
        value.textView = (android.widget.TextView)(((android.app.Activity)value).findViewById( 2131165353 ));
}

}

從上面的類中可以看到調(diào)用用findViewById的方法裆针。

參考鏈接:
https://blog.csdn.net/qq_20521573/article/details/82321755
http://77blogs.com/?p=199

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刨摩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子世吨,更是在濱河造成了極大的恐慌澡刹,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耘婚,死亡現(xiàn)場離奇詭異罢浇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)沐祷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門嚷闭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赖临,你說我怎么就攤上這事胞锰。” “怎么了思杯?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵胜蛉,是天一觀的道長挠进。 經(jīng)常有香客問我色乾,道長,這世上最難降的妖魔是什么领突? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任暖璧,我火速辦了婚禮,結(jié)果婚禮上君旦,老公的妹妹穿的比我還像新娘澎办。我一直安慰自己嘲碱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布局蚀。 她就那樣靜靜地躺著麦锯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琅绅。 梳的紋絲不亂的頭發(fā)上扶欣,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音千扶,去河邊找鬼料祠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛澎羞,可吹牛的內(nèi)容都是我干的髓绽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼妆绞,長吁一口氣:“原來是場噩夢啊……” “哼顺呕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起括饶,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤塘匣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巷帝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忌卤,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年楞泼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驰徊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堕阔,死狀恐怖棍厂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情超陆,我是刑警寧澤牺弹,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站时呀,受9級特大地震影響张漂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谨娜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一航攒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趴梢,春花似錦漠畜、人聲如沸币他。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝴悉。三九已至,卻和暖如春瘾敢,著一層夾襖步出監(jiān)牢的瞬間辫封,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工廉丽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倦微,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓正压,卻偏偏與公主長得像欣福,于是被迫代替她去往敵國和親焦履。 傳聞我的和親對象是個(gè)殘疾皇子嘉裤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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