Android開(kāi)發(fā)—APT注解處理器詳解

關(guān)于APT

APT(Annotation Processing Tool)是一種注解處理工具,它會(huì)對(duì)源文件進(jìn)行掃描找出相應(yīng)的Annotation并在注解處理器中進(jìn)行操作,具體操作由注解處理器也就是用戶自己去實(shí)現(xiàn),比如可以生成一些新的文件或者其他文件等,最終會(huì)把新生成的文件和源文件一起進(jìn)行編譯。

APT工具常用的有2個(gè),android-apt和Gradle2.2以后的annotationProcessor功能。

APT處理annotation的基本流程表示:

  • 定義注解,比如@Route
  • 自定義注解處理器,處理注解(如生成java文件等)
  • 使用注解處理器
APT操作@Route注解的大致步驟

android-apt

官方文檔

一個(gè)Gradle插件幫助Android Studio處理annotation processors,Gradle2.2以后Gradle提供annotationProcessor的功能可以完全代替android-apt,android-apt官網(wǎng)上作者也說(shuō)明了,不再維護(hù),并且谷歌明確表示Gradle 3.0.0+ 不再支持 android-apt 插件,所以推薦使用annotationProcessor。

android-apt主要有2個(gè)目的:
1、允許在注解處理器編譯的時(shí)候當(dāng)做依賴,但是在打包apk或者當(dāng)做類庫(kù)的時(shí)候不會(huì)打到里面;
2、設(shè)置生成的資源路徑以便能被Android studio正確訪問(wèn)到;

使用插件的時(shí)候如下配置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'
    }
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

傳遞編譯的參數(shù)

apt {
    arguments {
            resourcePackageName android.defaultConfig.applicationId
            androidManifestFile variant.outputs[0]?.processResources?.manifestFile
    }
}

由于android-apt已經(jīng)過(guò)時(shí)了,并且annotationProcessor也正式被Google扶正,所以具體apt的使用不在進(jìn)行演示,有興趣的同學(xué)可以訪問(wèn)android-apt主頁(yè)進(jìn)行學(xué)習(xí)。

annotationProcessor

官方文檔
annotationProcessor是Gradle2.2+內(nèi)置的功能,不需要額外引入其他插件,可以向下面這樣直接在gradle文件引入。

dependencies {
    // Adds libraries defining annotations to only the compile classpath.
    compileOnly 'com.google.dagger:dagger:version-number'
    // Adds the annotation processor dependency to the annotation processor classpath.
    annotationProcessor 'com.google.dagger:dagger-compiler:version-number'
}

這是引用第三方的注解處理器,我們實(shí)際開(kāi)發(fā)中可以自定義注解處理器,下面我們自定義一個(gè)簡(jiǎn)單的注解處理器。

自定義注解處理器

自定義注解處理器的話需要用到2個(gè)第三方庫(kù)AutoServiceJavaPoet ,還有Java自帶的AbstractProcessor。

  • AbstractProcessor:Java內(nèi)置注解處理器,注解處理器核心工作都在這個(gè)類進(jìn)行。
  • AutoService:Google開(kāi)源用來(lái)自動(dòng)注冊(cè)我們自己的注解處理器。
  • JavaPoet:Java代碼生成器,方便我們生成Java文件;

我們按照上文說(shuō)的APT處理annotation的基本流程來(lái)自定義。

1、定義注解,比如@Route
新建項(xiàng)目,然后新建一個(gè)Java module,叫annotationLib,里面定義我們自己的注解,關(guān)于注解的相關(guān)知識(shí)這里不再細(xì)說(shuō)具體可以參考這里

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Route {
    String value() ;
}

2、自定義注解處理器,處理注解(如生成java文件等)
再新建一個(gè)Java module,叫annotationCompiler,里面實(shí)現(xiàn)具體的注解處理器,配置gradle文件引入 AutoServiceJavaPoet,再依賴上我們之前定義的注解模塊。

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.squareup:javapoet:1.8.0'
    implementation project(':annotationLib')
}
sourceCompatibility = "7"
targetCompatibility = "7"

新建一個(gè)注解處理器繼承自AbstractProcessor:

@AutoService(Processor.class)
public class RouteProcessor extends AbstractProcessor {
    private Messager messager;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new HashSet<String>();
        annotataions.add(Route.class.getCanonicalName());
        return annotataions;
    }

    public void loggerInfo(String msg) {
        messager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && !set.isEmpty()) {
            loggerInfo("process start");
            StringBuilder printInfo = new StringBuilder();
            Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            try {
                if (routeElements != null && routeElements.size() > 0) {
                    printInfo.append(routeElements.size() + "個(gè)文件加了@Route注解!");
                }
            } catch (Exception e) {
                loggerInfo(e.getMessage());
            }
            //構(gòu)建參數(shù)
            ParameterSpec msg = ParameterSpec.builder(String.class, "msg")
                    .build();
            //構(gòu)建方法
            MethodSpec method = MethodSpec.methodBuilder("inject")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(msg)
                    .addStatement("$T.out.println($S+msg)", System.class, printInfo.toString())
                    .build();
            //構(gòu)建類
            TypeSpec helloWorld = TypeSpec.classBuilder("InjectHelper")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(method)
                    .build();
             //構(gòu)建文件并指定生成文件目錄
            JavaFile javaFile = JavaFile.builder("com.wzh.annotation", helloWorld)
                    .build();
            loggerInfo("process end");
            try {
                //把類、方法、參數(shù)等信息寫(xiě)入文件
                javaFile.writeTo(filer);
            } catch (IOException e) {
                loggerInfo("process exception");
                e.printStackTrace();
            }
            return true;
        }
        return false;
    }
}

然后我們?cè)谖覀僡pp模塊引用這個(gè)注解編譯器及注解,如下:

dependencies {
     ...
    annotationProcessor project(':annotationCompiler')
    implementation project(':annotationLib')
    ...
}

我們clean一下項(xiàng)目,然后rebuild一下,會(huì)發(fā)現(xiàn)如下目錄生成的文件:

app/build/generated/source/apt/debug/com/wzh/annotation/InjectHelper.java

import java.lang.String;
import java.lang.System;

public final class InjectHelper {
  public static void inject(String msg) {
    System.out.println("2個(gè)文件加了@Route注解!"+msg);
  }
}

這就是我們生成的文件,很簡(jiǎn)單一個(gè)InjectHelper類,里面一個(gè)靜態(tài)方法inject,打印出加@Route的文件個(gè)數(shù)。

3、使用注解處理器

@Route("main")
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectHelper.inject("調(diào)用生成類的方法");
    }
}
@Route("test")
public class TestClass {
     //empty class
}

這里我們演示很簡(jiǎn)單,在Activity上加了@Route的注解,并調(diào)用生成類的方法,然后在任意類都可以加注解,因?yàn)槲覀儧](méi)做任何注解類的限制,運(yùn)行程序輸出:

System.out: 2個(gè)文件加了@Route注解!調(diào)用生成類的方法

Kotlin使用注解處理器

首先app模塊引入注解處理器的時(shí)候需要引入kapt插件,在app下的gradle配置如下:

apply plugin: 'kotlin-kapt'
....
dependencies {
     ...
     //自定義注解處理器 module
    kapt project(':annotationCompiler')
    //自定義注解 module
    implementation project(':annotationLib')
    ...
}

其他配置基本一樣,文件生成的目錄變化,apt目錄變?yōu)?code>kapt:

app/build/generated/source/kapt/debug/com/wzh/annotation/InjectHelper.java

給注解處理器傳參數(shù)

在編譯之前可以傳遞需要的參數(shù)給注解處理器,我們?cè)赼pp模塊gradle傳遞module的名字給注解處理器:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                 //參數(shù)名 route_module_name,攜帶的數(shù)據(jù)就是當(dāng)前module的名字
                arguments = [route_module_name: project.getName()]
            }
        }
    }
}

在注解處理器init方法里接受參數(shù):

private String moduleName = null;

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    ...
    moduleName = processingEnvironment.getOptions().get("route_module_name");
    loggerInfo("moduleName = " + moduleName);
}

Rebuild項(xiàng)目的時(shí)候我們會(huì)在build控制臺(tái)看到如下輸出信息:

moduleName = app

APT的相關(guān)知識(shí)學(xué)習(xí)

自定義AbstractProcessor的時(shí)候我們會(huì)重寫(xiě)以下的方法:

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    // 初始化操作
}
@Override
public Set<String> getSupportedAnnotationTypes() {
    // 設(shè)置注解處理器需要處理的注解類型
}
@Override
public SourceVersion getSupportedSourceVersion() {
     //指定java版本
     return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //注解處理的核心方法
}

下面重點(diǎn)介紹initprocess方法

  • init(ProcessingEnvironment processingEnvironment) 方法
    此方法會(huì)被注解處理工具調(diào)用,參數(shù)ProcessingEnvironment 提供了一些實(shí)用的工具類Elements、Types和Filer等,如下表所示。
工具方法 功能
getElementUtils() 返回實(shí)現(xiàn)Elements接口的對(duì)象,用于操作元素的工具類
getFiler() 返回實(shí)現(xiàn)Filer接口的對(duì)象,用于創(chuàng)建文件、類和輔助文件
getMessager() 返回實(shí)現(xiàn)Messager接口的對(duì)象,用于報(bào)告錯(cuò)誤信息、警告提醒
getOptions() 返回指定的參數(shù)選項(xiàng),可在Gradle文件配置
getTypeUtils() 返回實(shí)現(xiàn)Types接口的對(duì)象,用于操作類型的工具類
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)方法
    此方法里面是我們進(jìn)行注解處理邏輯的地方。
    參數(shù)1 Set<? extends TypeElement> set:返回所有當(dāng)前注解處理器需要處理的Annotation.
    參數(shù)2 RoundEnvironment roundEnvironment:表示當(dāng)前或是之前的運(yùn)行環(huán)境,可以通過(guò)該對(duì)象查找到注解。

從roundEnvironment我們可以獲取到Element被注解的元素信息。下面我們寫(xiě)個(gè)實(shí)例來(lái)打印一下看看。

package com.wzh.annotation;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE,ElementType.TYPE_PARAMETER})
public @interface Test {
    String value();
}

這里定義的注解為了方便打印,支持注解到類、方法、變量、參數(shù)等。下面使用注解。

package com.wzh.annotation;
@Test("this is class TestClass")
public class TestClass<T> implements TestInterface{
    @Test("this is local field name")
    private String name = "my name is test";

    @Test("this is local method sayHello")
    private String sayHello(@Test("this is parameter msg") String msg){
        String hello = "my name is hello";
        return hello;
    }
}

然后在注解處理器去打印元素信息。

private void parseTestAnnotation(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Test.class);
        for (Element element : elements){ //遍歷所有元素
            if(element.getKind().equals(ElementKind.PACKAGE)){
                LoggerInfo("element--------------PACKAGE-------------------------------");
            } else if (element.getKind().equals(ElementKind.CLASS)){
                //被注解的元素是類
                TypeElement typeElement = (TypeElement) element;
                LoggerInfo("element--------------CLASS-------------------------------");
                //實(shí)現(xiàn)接口信息
                LoggerInfo("element:Interfaces = "+typeElement.getInterfaces().toString());
                //泛型參數(shù)
                LoggerInfo("element:TypeParameters = "+typeElement.getTypeParameters().toString());

                //element的父元素是包元素
                PackageElement packageElement = (PackageElement) element.getEnclosingElement();
                LoggerInfo("element:packageElement = "+packageElement.getQualifiedName());

            } else if (element.getKind().equals(ElementKind.FIELD)){
                //被注解的元素是全局變量
                LoggerInfo("element--------------FIELD-------------------------------");
                VariableElement variableElement = (VariableElement) element;
                //獲取變量類型
                LoggerInfo("element:typeSimpleName = "+ types.asElement(variableElement.asType()).getSimpleName());
            } else if (element.getKind().equals(ElementKind.PARAMETER)){
                //被注解的元素是參數(shù)
                LoggerInfo("element--------------PARAMETER-------------------------------");
            } else if (element.getKind().equals(ElementKind.METHOD)){
                //被注解的元素是方法
                LoggerInfo("element--------------METHOD-------------------------------");
                ExecutableElement executableElement = (ExecutableElement) element;
                //獲取方法的參數(shù)名
                LoggerInfo("element:Parameters = "+executableElement.getTypeParameters().toString());
                //獲取方法的返回值類型
                LoggerInfo("element:ReturnType = "+executableElement.getReturnType().toString());
            }
            //打印注解里面的值
            LoggerInfo("element:value = "+ element.getAnnotation(Test.class).value());
            //打印包名信息
            LoggerInfo("element:packageName = "+ elementUtils.getPackageOf(element).getQualifiedName());
            //被注解元素的名稱
            LoggerInfo("element:SimpleName = "+element.getSimpleName());
            //被注解元素的類型(String/int/float...)
            LoggerInfo("element:asType = "+element.asType().toString());
            //被注解元素的種類(PACKAGE、CLASS、METHOD、PARAMETER等)
            LoggerInfo("element:KindName = "+element.getKind().name());
            //獲取父元素的種類(局部變量的父元素是方法、方法及全局變量的父元素是類、類元素的父元素是包)
            LoggerInfo("element:EnclosingElementKindName = "+element.getEnclosingElement().getKind().name());
            //被注解元素的修飾 如:public static 等
            LoggerInfo("element:Modifiers = "+element.getModifiers().toString());
        }
    }

注: >> element--------------CLASS-------------------------------
注: >> element:Interfaces = com.wzh.annotation.TestInterface
注: >> element:TypeParameters = T
注: >> element:packageElement = com.wzh.annotation
注: >> element:value = this is class TestClass
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = TestClass
注: >> element:asType = com.wzh.annotation.TestClass<T>
注: >> element:KindName = CLASS
注: >> element:EnclosingElementKindName = PACKAGE
注: >> element:Modifiers = [public]
注: >> element--------------FIELD-------------------------------
注: >> element:typeSimpleName = String
注: >> element:value = this is local field name
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = name
注: >> element:asType = java.lang.String
注: >> element:KindName = FIELD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------METHOD-------------------------------
注: >> element:Parameters =
注: >> element:ReturnType = java.lang.String
注: >> element:value = this is local method sayHello
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = sayHello
注: >> element:asType = (java.lang.String)java.lang.String
注: >> element:KindName = METHOD
注: >> element:EnclosingElementKindName = CLASS
注: >> element:Modifiers = [private]
注: >> element--------------PARAMETER-------------------------------
注: >> element:value = this is parameter msg
注: >> element:packageName = com.wzh.annotation
注: >> element:SimpleName = msg
注: >> element:asType = java.lang.String
注: >> element:KindName = PARAMETER
注: >> element:EnclosingElementKindName = METHOD
注: >> element:Modifiers = []

由打印結(jié)果可以看到所有被注解的元素信息都被打印出來(lái)。

Element

Java文檔關(guān)于Element介紹
Element代表一個(gè)程序元素,如包、類、方法、變量、參數(shù)、接口泛型等的接口,其有多種子類分別代表不同的程序元素,如:ExecutableElement 方法元素, PackageElement 包元素, TypeElement 類元素, TypeParameterElement 形參元素, VariableElement 變量及參數(shù)元素等。之前的TestClass<T>可以對(duì)應(yīng)成下圖。

Element對(duì)應(yīng)圖

TypeMirror

TypeMirror是一個(gè)接口,表示Java編程語(yǔ)言中的類型。這些類型包括基本類型、引用類型、數(shù)組類型、類型變量和null類型等等。Element的 asType()返回TypeMirror類型的值,我們通過(guò)這個(gè)值得getKind()方法獲取元素的類型,這個(gè)類型有很多枚舉類型如:CHAR、ARRAY(數(shù)組)、FLOATEXECUTABLE(方法)等。

總結(jié)

關(guān)于注解處理器具體使用還有很多東西,就不一一寫(xiě)出來(lái)了,具體可以參考JAVA API,不得不說(shuō)注解處理器很強(qiáng)大,很多熱門框架都使用了APT,如:butterknife、Arouter、Dagger2、EventBus等。所以學(xué)好注解處理器還是比較重要的,接下來(lái)我們實(shí)戰(zhàn)一把,不看butterknife源碼的情況下實(shí)現(xiàn)簡(jiǎn)單的功能。Android開(kāi)發(fā)— APT之ButterKnife的簡(jiǎn)單功能實(shí)現(xiàn)

參考:
Java API
Java注解處理器
java-apt的實(shí)現(xiàn)之Element詳解

最后編輯于
?著作權(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)容

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