Android編譯時注解APT實戰(zhàn)(AbstractProcessor)

作者:Zyao89;轉(zhuǎn)載請保留此行,謝謝;


Java中的注解(Annotation)是一個很神奇的東西,特別現(xiàn)在有很多Android庫都是使用注解的方式來實現(xiàn)的。
我們并不討論那些在運行時(Runtime)通過反射機制運行處理的注解,而是討論在編譯時(Compile time)處理的注解。下面便入手學(xué)習(xí)下Java注解處理器。

基本概念

注解處理器是一個在javac中的,用來編譯時掃描和處理的注解的工具。你可以為特定的注解,注冊你自己的注解處理器。
注解處理器可以生成Java代碼,這些生成的Java代碼會組成 .java 文件,但不能修改已經(jīng)存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件,會同時與其他普通的手寫Java源代碼一起被javac編譯。

抽象處理器 AbstractProcessor

import javax.annotation.processing.AbstractProcessor;

我們每一個注解處理器都要繼承于AbstractProcessor,如下所示:

public class MyProcessor 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();
    }
}
  • init(ProcessingEnvironment processingEnvironment): 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù)。然而,這里有一個特殊的init()方法,它會被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類Elements,Types和Filer。后面我們將看到詳細的內(nèi)容。
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 這相當(dāng)于每個處理器的主函數(shù)main()。你在這里寫你的掃描、評估和處理注解的代碼,以及生成Java文件。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。后面我們將看到詳細的內(nèi)容。
  • getSupportedAnnotationTypes(): 這里你必須指定,這個注解處理器是注冊給哪個注解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的注解類型的合法全稱。換句話說,你在這里定義你的注解處理器注冊到哪些注解上。
  • getSupportedSourceVersion(): 用來指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支持Java 7的話,你也可以返回SourceVersion.RELEASE_7。我推薦你使用前者。

在Java 7以后,你也可以使用注解來代替getSupportedAnnotationTypes()和getSupportedSourceVersion(),像這樣:

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({
        "com.zyao89.ZyaoAnnotation"
})
public class MyProcessor extends AbstractProcessor
{
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment)
    {
        super.init(processingEnvironment);
    }

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

簡單實踐

  1. 首先我們來創(chuàng)建一個 @ZyaoAnnotation 注解,后面我們將用這個注解來自動生成一個java文件。

我們先來看下 @ ZyaoAnnotation 注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ZyaoAnnotation
{
    String name() default "undefined";

    String text() default "";
}

這時候我們可以在需要注解的類上增加我們的注解了,如:

@ZyaoAnnotation(
        name = "Zyao",
        text = "Hello !!! Welcome "
)
public class MainActivity extends AppCompatActivity
{

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

這時,注解都注解完了,那我們該怎么運行呢?請看下面。

處理器

這里我們通過代碼加注釋的方式,來一步步構(gòu)建我們想要的處理器。代碼如下:

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor
{
    private Types    mTypeUtils;
    private Elements mElementUtils;
    private Filer    mFiler;
    private Messager mMessager;

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

        //初始化我們需要的基礎(chǔ)工具
        mTypeUtils = processingEnv.getTypeUtils();
        mElementUtils = processingEnv.getElementUtils();
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }

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

    @Override
    public Set<String> getSupportedAnnotationTypes()
    {
        //支持的注解
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(ZyaoAnnotation.class.getCanonicalName());
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
    {
        //這里開始處理我們的注解解析了,以及生成Java文件
        return false;
    }
}

大家看到第一行@AutoService(Processor.class) 這個有木有?這是一個注解處理器,是Google開發(fā)的,用來生成META-INF/services/javax.annotation.processing.Processor文件的。

Gradle引入方式:

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

getSupportedAnnotationTypes()中,我們指定本處理器將處理 @ZyaoAnnotation 注解。

基礎(chǔ)工具解析

在init()中我們獲得如下引用:

  • Elements:一個用來處理Element的工具類,源代碼的每一個部分都是一個特定類型的Element,例如:
package com.example;    // PackageElement

public class Foo {        // TypeElement

    private int a;      // VariableElement
    private Foo other;  // VariableElement

    public Foo () {}    // ExecuteableElement

    public void setA (  // ExecuteableElement
                     int newA   // TypeElement
                     ) {}
}

舉例來說,假如你有一個代表public class Foo類的TypeElement元素,你可以遍歷它的孩子,如下:

TypeElement fooClass = ... ;  
for (Element e : fooClass.getEnclosedElements()){ // iterate over children  
    Element parent = e.getEnclosingElement();  // parent == fooClass
}

TypeElement并不包含類本身的信息。你可以從TypeElement中獲取類的名字,但是你獲取不到類的信息,例如它的父類。這種信息需要通過TypeMirror獲取。你可以通過調(diào)用elements.asType()獲取元素的TypeMirror。

  • Types:一個用來處理TypeMirror的工具類;(后面會使用到,在進行講解)

  • Filer:正如這個名字所示,使用Filer你可以創(chuàng)建文件。

搜索@ZyaoAnnotation注解

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
    {//這里開始處理我們的注解解析了,以及生成Java文件

        // 遍歷所有被注解了@ZyaoAnnotation的元素
        for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(ZyaoAnnotation.class)) {
            ...
        }


        return false;
    }

roundEnvironment.getElementsAnnotatedWith(ZyaoAnnotation.class)返回所有被注解了@ZyaoAnnotation的元素的列表。你可能已經(jīng)注意到,我們并沒有說“所有被注解了@ZyaoAnnotation的類的列表”,因為它真的是返回Element的列表。請記?。篍lement可以是類、方法、變量等。所以,接下來,我們必須檢查這些Element是否是一個類:

// 遍歷所有被注解了@ZyaoAnnotation的元素
        for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(ZyaoAnnotation.class)) {
            // 檢查被注解為@Factory的元素是否是一個類
            if (annotatedElement.getKind() != ElementKind.CLASS) {
                error(annotatedElement, "Only classes can be annotated with @%s",
                        ZyaoAnnotation.class.getSimpleName());
                return true; // 退出處理
            }

            //解析,并生成代碼
            analysisAnnotated(annotatedElement);
        }

這里就配合了TypeMirror使用EmentKind或者TypeKind進行元素類型判斷。

error()日志和錯誤信息打?。?/p>

private void error(Element e, String msg, Object... args) {
        mMessager.printMessage(
                Diagnostic.Kind.ERROR,
                String.format(msg, args),
                e);
    }

除了Diagnostic.Kind.ERROR日志等級,還有其它,如:

public static enum Kind {
        ERROR,
        WARNING,
        MANDATORY_WARNING,
        NOTE,
        OTHER;

        private Kind() {
        }
    }

代碼生成

編寫解析和生成的代碼格式:

private static final String SUFFIX = "$$ZYAO";

private void analysisAnnotated(Element classElement)
    {
        ZyaoAnnotation annotation = classElement.getAnnotation(ZyaoAnnotation.class);
        String name = annotation.name();
        String text = annotation.text();

//        TypeElement superClassName = mElementUtils.getTypeElement(name);
        String newClassName = name + SUFFIX;

        StringBuilder builder = new StringBuilder()
                .append("package com.zyao89.demoprocessor.auto;\n\n")
                .append("public class ")
                .append(newClassName)
                .append(" {\n\n") // open class
                .append("\tpublic String getMessage() {\n") // open method
                .append("\t\treturn \"");

        // this is appending to the return statement
        builder.append(text).append(name).append(" !\\n");


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


        try { // write the file
            JavaFileObject source = mFiler.createSourceFile("com.zyao89.demoprocessor.auto."+newClassName);
            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
        }

        info(">>> analysisAnnotated is finish... <<<");
    }

以上的連接字符串工作,可以使用JavaPoet開源庫進行編寫,提升效率。

compile 'com.squareup:javapoet:1.7.0'

最后build一下工程,生成最終的java文件 Zyao$$ZYAO.java

生成的文件路徑:
/DemoAbstractProcessor/app/build/generated/source/apt/debug/com/zyao89/demoprocessor/auto/Zyao$$ZYAO.java

文件內(nèi)容:

package com.zyao89.demoprocessor.auto;

public class Zyao$$ZYAO {

    public String getMessage() {
        return "Hello !!! Welcome Zyao !\n";
    }
}

運行

@ZyaoAnnotation(
        name = "Zyao",
        text = "Hello !!! Welcome "
)
public class MainActivity extends AppCompatActivity
{

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

    }

    public void changeText(View view) {
        Zyao$$ZYAO zyao$$ZYAO = new Zyao$$ZYAO();
        String message = zyao$$ZYAO.getMessage();
        ((TextView)view).setText(message);
    }
}

最終運行我們的程序,點擊文本,可得到我們的getMessage內(nèi)容。

演示圖

演示圖

Github項目地址:zyao89/DemoAbstractProcessor


作者:Zyao89;轉(zhuǎn)載請保留此行,謝謝;

個人博客:https://zyao89.cn

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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