注解 - APT編譯時(shí)注解處理器

簡(jiǎn)介

APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具,確切的說(shuō)它是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解。注解處理器以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成.java文件作為輸出。

簡(jiǎn)單來(lái)說(shuō)就是在編譯期,通過(guò)注解生成.java文件。

作用

使用APT的優(yōu)點(diǎn)就是方便、簡(jiǎn)單,可以少些很多重復(fù)的代碼。根據(jù)規(guī)則,幫我們生成代碼、生成類文件,如ButterKnife、Dagger、EventBus等注解框架。

Element 程序元素
元素 描述
PackageElement 表示一個(gè)包程序元素,提供對(duì)有關(guān)包及其成員的信息的訪問(wèn)
ExecutableElement 表示某個(gè)類或接口的方法、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗?/td>
TypeElement 表示一個(gè)類或接口程序元素,提供對(duì)有關(guān)類型及其成員的信息的方法
VaribaleElement 表示一個(gè)字段、enum常量、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)
常用API
方法 描述
getEnclosedElements() 返回該元素直接包含的子元素
getEnclosingElement() 返回包含該element的父element,與上一個(gè)方法相反
getKind() 返回element的類型,判斷是那種element
getModifiers() 獲取修飾關(guān)鍵字,入public static final 等關(guān)鍵字
getSimpleName() 獲取名字,不帶包名
getQualifiedName() 獲取全名,如果是類的話,包含完整的包名路徑
getParameters() 獲取方法的參數(shù)元素,每個(gè)元素是一個(gè)VariableElement
getReturnType() 獲取方法元素的返回值
getConstantValue() 如果屬性變量被final修飾,則可以使用該方法獲取它的值

Demo

項(xiàng)目結(jié)構(gòu)

  • 創(chuàng)建Android Module命名為app
  • 創(chuàng)建Java library Module命名為 apt-annotation 存放自定義注解
  • 創(chuàng)建Java library Module命名為 apt-compiler 依賴 apt-annotation、auto-service 指定注解處理器
  • 創(chuàng)建Android library Module 命名為 apt-library 存放工具類

==注==:為什么兩個(gè)模塊一定要是Java Library?如果創(chuàng)建Android Library模塊會(huì)發(fā)現(xiàn)不能找到AbstractProcessor這個(gè)類,這是因?yàn)锳ndroid平臺(tái)是基于OpenJDK的,而OpenJDK中不包含APT的相關(guān)代碼。因此,在使用APT時(shí),必須在Java Library中進(jìn)行。

環(huán)境配置

apt-annotation 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
    ...
    // DES: 導(dǎo)入annotation庫(kù), 因lib中使用到了內(nèi)置注解
    implementation 'androidx.annotation:annotation:1.0.0@jar'
}
// DES: 指定編碼格式
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
// DES: 指定Java JDK 兼容及目標(biāo)版本
// DES: 7 表示 Java JDK 1.7
sourceCompatibility = "7"
targetCompatibility = "7"
apt-compiler 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
    ...
    // DES: 依賴自定義注解
    implementation project(":apt-annotation")
    // DES: 導(dǎo)入官方APT處理Service
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

    // DES: 導(dǎo)入JavaPoet庫(kù)用于類調(diào)用的形式來(lái)生成Java代碼
    implementation 'com.squareup:javapoet:1.9.0'
}
// DES: 指定編碼格式
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
// DES: 指定Java JDK 兼容及目標(biāo)版本
// DES: 7 表示 Java JDK 1.7
sourceCompatibility = "7"
targetCompatibility = "7"
版本問(wèn)題
// Android Studio 3.3.2 + Gradle 4.10.1 (臨界版本)
// 注冊(cè)注解,并對(duì)其生成META-INF的配置信息,rc2在gradle 5.1.1后有坑
// AS3.3.2 + Gradle 4.10.1 + auto-service:1.0-rc2
implementation 'com.google.auto.service:auto-service:1.0-rc2'

// Android Studio 3.4.1 + Gradle 5.1.1(向下兼容)
// AS3.4.1 + Gradle 5.1.1 + auto-service:1.0-rc4
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
apt-library 無(wú)需依賴
app 對(duì)應(yīng)的 build.gradle 進(jìn)行配置
dependencies {
    ...
    // 導(dǎo)入library
    implementation project(":apt-library")
    // 導(dǎo)入自定義注解
    implementation project(":apt-annotation")
    // 指定注釋處理器
    annotationProcessor project(":apt-compiler")
    ...
}

相關(guān)代碼

apt-annotation 自定義注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定義注解 @BindView 牛油刀
 * @author XGY
 */
@Target(ElementType.FIELD) // 作用于成員屬性上
@Retention(RetentionPolicy.RUNTIME) // 指定為運(yùn)行時(shí)
public @interface BindView {
    int value();
}

apt-compiler
節(jié)點(diǎn)信息
/**
 * 節(jié)點(diǎn)信息Bean
 * @author XGY
 */
public class NodeInfo {
    /** 包路徑 */
    private String packageName;
    /** 節(jié)點(diǎn)所在類名稱 */
    private String className;
    /** 節(jié)點(diǎn)類型名稱 */
    private String typeName;
    /** 節(jié)點(diǎn)名稱 */
    private String nodeName;
    /** 注解的value */
    private int value;

    public NodeInfo(String packageName, String className, String typeName, 
                        String nodeName, int value) {
        this.packageName = packageName;
        this.className = className;
        this.typeName = typeName;
        this.nodeName = nodeName;
        this.value = value;
    }
    public String getPackageName() { return packageName; }
    public String getClassName() { return className; }
    public String getTypeName() { return typeName; }
    public String getNodeName() { return nodeName; }
    public int getValue() { return value; }
}
處理器
import com.example.annotation.BindView;
import com.example.compiler.bean.NodeInfo;
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.Processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * 自定義注解 @BindView 編譯器
 * @author XGY
 */
@AutoService(Processor.class) // 指定注解處理器
@SupportedAnnotationTypes("com.example.annotation.BindView") // 指定需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) // 指定Java JDK編譯版本
public class BindViewProcessor extends AbstractProcessor {

    /** Element操作類 */
    private Elements mElementUtils;
    /** 類信息工具類 */
    private Types mTypeUtils;
    /** 日志工具類 */
    private Messager mMessager;
    /** 文件創(chuàng)建工具類 */
    private Filer mFiler;

    /** 節(jié)點(diǎn)信息緩存 */
    private Map<String, List<NodeInfo>> mCache = new HashMap<>();

    /** 初始化 */
    @Override
    public synchronized void init(ProcessingEnvironment environment) {
        super.init(environment);
        // 獲取相關(guān)工具
        mElementUtils = environment.getElementUtils();
        mTypeUtils = environment.getTypeUtils();
        mMessager = environment.getMessager();
        mFiler = environment.getFiler();

        // 打印Build提示
        mMessager.printMessage(Diagnostic.Kind.NOTE, "開始處理自定義 @BindView 注解");
    }

    /** 處理注解 */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 判斷是否有使用 @BindView 注解
        if (annotations != null && !annotations.isEmpty()) {
            // 獲取所有 @BindView 節(jié)點(diǎn)
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
            // 判斷節(jié)點(diǎn)集合不為空
            if (elements != null && !elements.isEmpty()) {
                // 循環(huán)遍歷節(jié)點(diǎn)
                for (Element element : elements) {
                    // 獲取節(jié)點(diǎn)包信息
                    String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
                    // 獲取節(jié)點(diǎn)類信息,由于 @BindView 作用于成員屬性上,所以這里使用 getEnclosingElement() 獲取父節(jié)點(diǎn)信息
                    String className = element.getEnclosingElement().getSimpleName().toString();
                    // 獲取節(jié)點(diǎn)類型
                    String typeName = element.asType().toString();
                    // 獲取節(jié)點(diǎn)標(biāo)記的屬性名稱
                    String simpleName = element.getSimpleName().toString();
                    // 獲取注解的值
                    int value = element.getAnnotation(BindView.class).value();

                    // 打印
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "className:" + className);
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName);
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "simpleName:" + simpleName);
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "value:" + value);

                    // 緩存KEY
                    String key = packageName + "." + className;
                    // 緩存節(jié)點(diǎn)信息
                    List<NodeInfo> nodeInfos = mCache.get(key);
                    // 判斷是否為空
                    if (nodeInfos == null) {
                        // 初始化
                        nodeInfos = new ArrayList<>();
                        // 載入
                        nodeInfos.add(new NodeInfo(packageName, className, typeName, simpleName, value));
                        // 緩存
                        mCache.put(key, nodeInfos);
                    } else {
                        // 載入
                        nodeInfos.add(new NodeInfo(packageName, className, typeName, simpleName, value));
                    }
                }
                // 判斷臨時(shí)緩存是否不為空
                if (!mCache.isEmpty()) {
                    // 遍歷臨時(shí)緩存文件
                    for (Map.Entry<String, List<NodeInfo>> stringListEntry : mCache.entrySet()) {
                        try {
                            // 創(chuàng)建文件
                            createFile(stringListEntry.getValue());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

                return true;
            }
        }

        return false;
    }

    /** 創(chuàng)建Java文件 */
    private void createFile(List<NodeInfo> infos) throws IOException {
        // 獲取第一個(gè)節(jié)點(diǎn)用來(lái)獲取信息
        NodeInfo info = infos.get(0);
        // 生成的類名稱
        String className = info.getClassName() + "$$ViewBinding";
        // JavaFileObject
        JavaFileObject file = mFiler.createSourceFile(info.getClassName() + "." + className);
        // Writer
        Writer writer = file.openWriter();
        // 設(shè)置包路徑
        writer.write("package " + info.getPackageName() + ";\n\n");
        // 設(shè)置類名稱
        writer.write("public class " + className + " {\n\n");
        writer.write("\tpublic static void bind(" + info.getClassName() + " target) {\n");
        // 循環(huán)遍歷設(shè)置方法體
        for (NodeInfo node : infos) {
            writer.write("\t\ttarget." + node.getNodeName() + " = (" + node.getTypeName() +
                    ") target.findViewById(" + node.getValue() + ");\n");
        }
        writer.write("\t}\n");
        writer.write("}");
        // 關(guān)閉
        writer.close();
    }
}

==注意:==

// 指定注解處理器
@AutoService(Processor.class) 
// 指定需要處理的注解
@SupportedAnnotationTypes("com.example.annotation.BindView") 
// 指定Java JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)

關(guān)于文件生成可以使用JavaPoet,
相關(guān)地址:
Github


apt-library 工具類
import android.app.Activity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 自定義牛油刀工具類
 * @author XGY
 */
public final class ButterKnife {

    /** 綁定 */
    public static void bind(Activity target) {
        try {
            // Activit類
            Class clazz = target.getClass();
            // 反射獲取apt生成的指定類
            Class<?> bindViewClass = Class.forName(clazz.getName() + "$$ViewBinding");
            // 獲取它的方法
            Method method = bindViewClass.getMethod("bind", clazz);
            // 執(zhí)行方法
            method.invoke(bindViewClass.newInstance(), target);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

app 中使用
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);
        // 綁定
        ButterKnife.bind(this);
        // 設(shè)置文案
        textView.setText("ButterKnife 綁定成功");
    }
}
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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