1. 程式人生 > >Android中使用AbstractProcessor在編譯時生成程式碼

Android中使用AbstractProcessor在編譯時生成程式碼

1.概述

在現階段的Android開發中,註解越來越流行起來,比如ButterKnife,Retrofit,Dragger,EventBus等等都選擇使用註解來配置。按照處理時期,註解又分為兩種型別,一種是執行時註解,另一種是編譯時註解,執行時註解由於效能問題被一些人所詬病。編譯時註解的核心依賴APT(Annotation Processing Tools)實現,原理是在某些程式碼元素上(如型別、函式、欄位等)添加註解,在編譯時編譯器會檢查AbstractProcessor的子類,並且呼叫該型別的process函式,然後將添加了註解的所有元素都傳遞到process函式中,使得開發人員可以在編譯器進行相應的處理,例如,根據註解生成新的Java類,這也就是EventBus,Retrofit,Dragger等開源庫的基本原理。
Java API已經提供了掃描原始碼並解析註解的框架,你可以繼承AbstractProcessor類來提供實現自己的解析註解邏輯。下邊我們將學習如何在Android Studio中通過編譯時註解生成java檔案。

2.建立名為processor的module

首先使用Android Studio建立一個Android的project。然後開始建立一個名為processor的java library。
點選file->new->new module如圖

我們需要建立一個非Android的library,注意一定要選擇Java Library

3.相容性配置

由於Android目前不是完全支援Java 8的語言特性,會導致編譯出錯。這裡將專案的源和目標相容性值保留為 Java 7。
開啟app模組下的build.gradle

在android標籤下新增 compile options

compileOptions {
   sourceCompatibility JavaVersion.VERSION_1_7
   targetCompatibility JavaVersion.VERSION_1_7
}

如圖

然後開啟processor library的build.gradle

新增

sourceCompatibility = 1.7
targetCompatibility = 1.7

4.建立Annotation

在processor模組下建立一個註解類

命名為CustomAnnotation

具體內容如下

5.建立註解處理器

Processor繼承自AbstractProcessor類,@SupportedAnnotationTypes中填寫待處理的註解全稱,@SupportedSourceVersion表示處理的JAVA版本。

@SupportedAnnotationTypes(“<待處理註解類路徑>”)
@SupportedSourceVersion(SourceVersion.RELEASE_7)

在註解類上右鍵選擇copy reference即可快速的獲得類的全稱

實現AbstractProcessor的方法

編譯時候將會執行process方法

向process方法寫入下述程式碼

 @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        StringBuilder builder = new StringBuilder()
                .append("package com.yuntao.annotationprocessor.generated;\n\n")
                .append("public class GeneratedClass {\n\n") // open class
                .append("\tpublic String getMessage() {\n") // open method
                .append("\t\treturn \"");


        // for each javax.lang.model.element.Element annotated with the CustomAnnotation
        for (Element element : roundEnv.getElementsAnnotatedWith(CustomAnnotation.class)) {
            String objectType = element.getSimpleName().toString();


            // this is appending to the return statement
            builder.append(objectType).append(" says hello!\\n");
        }


        builder.append("\";\n") // end return
                .append("\t}\n") // close method
                .append("}\n"); // close class


        try { // write the file
            JavaFileObject source = processingEnv.getFiler().createSourceFile("com.yuntao.annotationprocessor.generated.GeneratedClass");


            Writer writer = source.openWriter();
            writer.write(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            // Note: calling e.printStackTrace() will print IO errors
            // that occur from the file already existing after its first run, this is normal
        }


        return true;
    }

此處呼叫process方法生成了一個Java類檔案,該類包含一個getMessage方法,該方法會返回一個字串,其中字串包含被@CustomAnnotation修飾的類的名稱。這裡主要在process方法中獲取註解修飾類的名稱。先看下生成的程式碼如下。

注意:由於這個檔案是在build過程中建立的,所以只有build成功之後才可以檢視到它。對應該類生成在以下目錄中:

app/build/generated/source/apt/debug/<package>/GeneratedClass.java

這裡便於演示我們只是簡單的生成了一個Java原始檔,當然如果要生成更復雜的檔案,可以利用第三方工具,例如javapoet

6.建立resource

建立好註解處理器後,我們需要告訴編譯器在編譯的時候使用哪個註解處理器,這裡就需要建立javax.annotation.processing.Processor檔案

在processor模組下,main目錄中建立一個resources資料夾,然後下邊在建立META-INF/services,最後裡邊一個javax.annotation.processing.Processor檔案,如下:

在此檔案中寫入註解處理器的類全稱

com.yuntao.annotationprocessor.processor.CustomAnnotationProcessor

7.新增android-apt

在project下的build.gradle中新增apt外掛

新增依賴

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然後在app中的build.gradle新增

apply plugin: 'com.neenbedankt.android-apt'

8.設定build的依賴

這一節主要講的是把processor模組中的註解,註解處理器編譯生成一個jar,然後把這個jar包複製到app模組下。然後讓app依賴引用這個jar。
首先配置app的build.gradle依賴項,新增jar依賴,看最後一行。

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'
    testCompile 'junit:junit:4.12'
    compile files('libs/processor.jar')
}

然後我們編寫一個gradle task,把生成的jar檔案複製到app/libs目錄中。

task processorTask(type: Exec) {
    commandLine 'cp', '../processor/build/libs/processor.jar', 'libs/'
}

最後我們建立task的依賴順序,app:preBuild依賴我們寫的processorTask, processorTask依賴 :processor:build:
意思就是processor build完成後把jar複製到app,然後在執行app:preBuild。

processorTask.dependsOn(':processor:build')
preBuild.dependsOn(processorTask)

最後配置完的build.gradle檔案如下。

執行下編譯命令/gradlew :app:clean :app:build,看task執行順序

同時在app/libs目錄下可以檢視到生成的jar

9.使用註解

我們在MainActivity的類與onCreate方法上使用@CustomAnnotation

現在加上註解之後還沒有生成我們需要的java檔案,需要rebuild下才會生成,可以在Android Studio中選擇Build>Rebuild或者在終端執行

/gradlew :app:clean :app:build

然後我們可以在下述位置檢視到生成的Java檔案

app/build/generated/source/apt/debug/package/GeneratedClass.java

10.驗證能否使用

在MainActivity的onCreate方法彈窗,顯示下生成的GeneratedClass的getMessage方法返回的資訊。程式碼如下

private void showAnnotationMessage() {
        GeneratedClass generatedClass = new GeneratedClass();
        String message = generatedClass.getMessage();
        // android.support.v7.app.AlertDialog
        new AlertDialog.Builder(this)
                .setPositiveButton("Ok", null)
                .setTitle("Annotation Processor Messages")
                .setMessage(message)
                .show();
    }

執行後結果

11.AnnotationProcessor中除錯程式碼

一般在寫程式碼的時候除錯是不可避免的,最後一節我們講下如何除錯。
首先程式碼中對process()方法設定程式碼斷點。

設定gradle daemon埠和JVM引數。把下面兩行加入到你的gradle.properties檔案。

org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

在命令列中執行gradle daemon來啟動守護執行緒。

gradle --daemon

在Android Studio建立Remote Debugger

Remote Debugger 配置,我們在這裡使用預設設定。IP:localhost,埠:5005。

然後點選下圖按鈕執行,它就會連線到daemon執行緒中了。

最後我們用gradle命令來執行構建。

gradle clean assembleDebug

既然我們已經啟動了守護執行緒,Remote Debugger將觸發斷點並掛起構建執行。

最後總結下本編文章,主要講了AbstractProcessor的使用,在Android Studio構建過程建立Java檔案,同時使用他的例子,最後補充了一下如何除錯Processor的方法。