1. 程式人生 > >安卓註解處理器-processor

安卓註解處理器-processor

最近在學習安卓開源框架發現,很多的開源框架都使用到了註解處理器,例如EventBus3.0。本文通過一個簡單的Demo來介紹如何使用註解處理器。如果喜歡的話,歡迎大家給star。

Demo需求描述

使用者通過執行一個傳入引數為A(類物件)的靜態方法,該方法會最終把引數A中加了特定註解的所有方法執行一遍。

需求實現

專案描述

整個專案分為四個部分:

  • 註解–要使用的註解型別,這部分通常也可以放在lib中;
  • 註解處理器–要對註解進行處理的邏輯,包括收集有特定註解型別的方法資訊以及生成特定的java檔案;
  • lib–封裝合適的介面,供具體呼叫方呼叫;
  • sample–具體的呼叫方邏輯。

首先新建一個安卓工程,點選執行展示的是hello world。

註解

在上述工程中new->Module->Java Library,新建一個Java Library Module,命名為annotation。在該Module下建立一個檔案AnnotationTest.java,

註解

AnnotationTest.java裡面程式碼如下:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface AnnotationTest {
    String name() default "test";
}

1、註解@Retention按生命週期來劃分可分為3類:

  • RetentionPolicy.SOURCE:註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄;
  • RetentionPolicy.CLASS:註解被保留到class檔案,當jvm載入class檔案時候被遺棄,這是預設的生命週期;
  • RetentionPolicy.RUNTIME:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在。

這3個生命週期分別對應於:Java原始檔(.java檔案) —> .class檔案 —> 記憶體中的位元組碼。

2、註解@Target表示修飾的註解能使用的範圍,ElementType.METHOD表示@AnnotationTest註解只能作用在方法上。

註解處理器

參照上部分,在工程中new->Module->Java Library,新建一個Java Library Module, 在該Module下建立一個檔案ProcessorTest.java。在該Module下的build.gradle的dependencies中新增如下配置:

// 自動為processor註冊
implementation 'com.google.auto.service:auto-service:1.0-rc2'
// 該Module依賴上部分建立的annotation Module
implementation project(':annotation')

com.google.auto.service:auto-service:1.0-rc2依賴的作用是為註解處理器自動註冊,它會生成META-INF資料夾。

註解處理器ProcessorTest的定義如下,其中@AutoService(Processor.class)就是build.gradle中加的依賴幫助其自動註冊。

@AutoService(Processor.class) // 自動為ProcessorTest註冊,生成META-INF檔案
public class ProcessorTest extends AbstractProcessor{

註解處理器ProcessorTest主要包含以下幾個部分:

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

    mMessager = processingEnvironment.getMessager();
    mFiler = processingEnvironment.getFiler();
}

init方法是註解處理器會自動呼叫的初始化方法,其中mFiler是用來生成java原始檔的工具,mMessager是用來列印日誌的,它們的具體使用會在後面介紹。

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

getSupportedAnnotationTypes()方法返回該註解處理器支援的註解型別,這裡返回的就是我們之前宣告的新的註解型別@AnnotationTest。

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

getSupportedSourceVersion()方法一般就按照上述實現就行。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
	// 列印日誌
    mMessager.printMessage(Diagnostic.Kind.NOTE, "process start");
    Map<String, List<String>> collectInfos = new HashMap<>();
    for (TypeElement annotation: annotations){
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
        for (Element element: elements){
        	// 檢查element是否符合我們定義的規範
            if (!checkValid(element)){
                mMessager.printMessage(Diagnostic.Kind.NOTE, "checkValid not pass");
                return false;
            }else {
                ExecutableElement executableElement = (ExecutableElement) element;
                // 獲取被註解的方法所在的類
                TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
                // 獲取類的全名,包括包名
                String classFullName = typeElement.getQualifiedName().toString();
                // 被註解的方法的名字
                String methodName = executableElement.getSimpleName().toString();
                List<String> methods = collectInfos.get(classFullName);
                if (methods == null){
                    methods = new ArrayList<>();
                    collectInfos.put(classFullName, methods);
                }
                methods.add(methodName);
            }
        }
    }

    for (Map.Entry<String, List<String>> entry: collectInfos.entrySet()){
        mMessager.printMessage(Diagnostic.Kind.NOTE, entry.getKey());
        // 生成java原始檔
        createJavaFile(entry.getKey(), entry.getValue());
    }

    return true;
}

process方法是我們的主要邏輯處理的地方,主要邏輯就是收集所有有@AnnotationTest註解的方法以及其所在的類資訊,然後根據每個類資訊,生成一個新的類檔案,並在新的類檔案的特定方法中呼叫所有關聯的註解方法。生成java原始檔將使用Filer物件,具體如何使用請下載demo看原始碼。

注:

1、當你點選buid project時,註解處理器將會執行,而Messager物件打印出來的日誌資訊可以在Gradle Console視窗中看到。

2、如果你在該Module中使用中文註解,因為該Module為java library,可能會報GBK編碼錯誤,解決辦法是在該Module的build.gradle中新增如下程式碼:

//指定編譯的編碼
tasks.withType(JavaCompile){
    options.encoding = "UTF-8"
}

Lib

在工程中new->Module->Android Library ,新建一個Android Library Module,封裝介面給呼叫方使用,具體邏輯請參考demo。

最終該demo的功能是點選Hello world文字,會依此執行MainActivity中使用@AnnotationTest註解的方法。