如何使用Android註解處理器

我們就可以結合今天的Annotation Processing Tool(APT)來自定義註解處理器。

註解處理器簡單解釋就是收集我們標記的註解,處理註解上提供的信息。

本篇用我之前寫的Saber舉例說明。

1.定義註解

推薦New -> Module -> Java Library,新建一個Java Library Module,命名為xx-annotation。用來單獨存放註解。

既然是註解處理器,那麼首先需要有註解。自定義一個註解使用@interface關鍵字。

public @interface LiveData {
}

然後我們需要用到註解的註解,也就是元註解來控制註解的行為。這裡我簡單介紹一些元註解。

  • Retention 表示註解的保留范圍。值用RetentionPolicy枚舉類型表示,分為CLASSRUNTIMESOURCE
  • Target 表示註解的使用范圍。值用ElementType枚舉類型表示,有TYPE(作用於類)、FIELD(作用於屬性)、METHOD(作用於方法)等。

這裡我的@LiveData註解作用是為瞭便於創建LiveData,而創建時需要知道數據類型。所以這個註解的使用范圍就是類和屬性。

其次這個註解處理生成模板代碼後,我們不需要保留在編譯後的.class文件中。所以可以使用SOURCE

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface LiveData {
}    
    

2.實現處理器

首先New -> Module -> Java Library,新建一個Java Library Module,命名為xx-complier。用來存放註解處理器。

創建一個繼承AbstractProcessor的類LiveDataProcessor

public class LiveDataProcessor extends AbstractProcessor {

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

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(LiveData.class.getCanonicalName());
    }

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

需要實現三個方法,getSupportedSourceVersion 指定支持的Java版本,getSupportedAnnotationTypes指定處理的註解。process是處理註解的地方。

不過這裡還需要初始化一些工具,可以重寫init 來實現。

private Elements elementUtils; // 操作元素的工具類
private Filer filer;  // 用來創建文件
private Messager messager; // 用來輸出日志、錯誤或警告信息

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    this.elementUtils = processingEnv.getElementUtils();
    this.filer = processingEnv.getFiler();
    this.messager = processingEnv.getMessager();
}

下面就是重點process瞭,我們的註解作用范圍是類和屬性。所以我們需要將同一個類下的註解整理到一起。這裡使用getElementsAnnotatedWith循環所有註解元素。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    for (Element element : roundEnv.getElementsAnnotatedWith(LiveData.class)) {
        if (element.getKind() == ElementKind.FIELD) {
            handlerField((VariableElement) element); // 表示一個字段
        }
        if (element.getKind() == ElementKind.CLASS) {
            handlerClass((TypeElement) element); // 表示一個類或接口
        }
        // ExecutableElement表示某個類或接口的方法
    }
    return true;
}

private void handlerClass(TypeElement element) {
    ClassEntity classEntity = new ClassEntity(element);
    String className = element.getSimpleName().toString();

    if (classEntityMap.get(className) == null) {
        classEntityMap.put(className, classEntity);
    }
}

private void handlerField(VariableElement element) {
    FieldEntity fieldEntity = new FieldEntity(element);
    String className = fieldEntity.getClassSimpleName();
    if (classEntityMap.get(className) == null) {
        classEntityMap.put(className,
                new ClassEntity((TypeElement) element.getEnclosingElement()));
    }
    ClassEntity classEntity = classEntityMap.get(className);
    classEntity.addFieldEntity(fieldEntity);
}

上面代碼中的element.getKind()獲取的是元素種類,對應的基本是上面元註解ElementType的類型。

ElementType ElementKind Element
TYPE CLASS TypeElement
FIELD FIELD VariableElement
METHOD METHOD ExecutableElement

下面是封裝的簡易element,便於實際的使用。

class ClassEntity {
    private final TypeElement element;
    private final Name classSimpleName;
    private final Map<String, FieldEntity> fields = new HashMap<>();

    public ClassEntity(TypeElement element) {
        this.element = element;
        this.classSimpleName = element.getSimpleName();
    }

    public String getClassSimpleName() {
        return classSimpleName.toString();
    }

    public void addFieldEntity(FieldEntity fieldEntity) {
        String fieldName = fieldEntity.getElement().toString();
        if (fields.get(fieldName) == null) {
            fields.put(fieldName, fieldEntity);
        }
    }

    public TypeElement getElement() {
        return element;
    }

    public Map<String, FieldEntity> getFields() {
        return fields;
    }
}

class FieldEntity {
    private VariableElement element;
    private String classSimpleName;

    public FieldEntity(VariableElement element) {
        this.element = element;
        this.classSimpleName = element.getEnclosingElement().getSimpleName().toString();
    }
    public VariableElement getElement() {
        return element;
    }

    public String getClassSimpleName() {
        return classSimpleName;
    }
}

下面就是使用JavaPoet來生成代碼,具體使用見JavaPoet使用攻略。這部分直接上代碼:

private JavaFile brewViewModel(Map.Entry<String, ClassEntity> item) {
    ClassEntity classEntity = item.getValue();
    LiveData liveData = classEntity.getElement().getAnnotation(LiveData.class);
    /*類名*/
    String className = classEntity.getElement().getSimpleName().toString() + "ViewModel";

    ClassName viewModelClazz = ClassName.get("androidx.lifecycle", "ViewModel");


    TypeSpec.Builder builder = TypeSpec
            .classBuilder(className)
            .addModifiers(Modifier.PUBLIC)
            .superclass(viewModelClazz);

    // 優先執行類LiveData註解
    if (liveData != null){
        TypeName valueTypeName = ClassName.get(classEntity.getElement());
        brewLiveData(classEntity.getClassSimpleName(), valueTypeName, builder);
    }else {
        Map<String, FieldEntity> fields = classEntity.getFields();

        for (FieldEntity fieldEntity : fields.values()){
            String fieldName = StringUtils.upperCase(fieldEntity.getElement().getSimpleName().toString());
            TypeName valueTypeName = ClassName.get(fieldEntity.getElement().asType());
            brewLiveData(fieldName, valueTypeName, builder);
        }
    }

    TypeSpec typeSpec = builder.build();
    // 指定包名
    return JavaFile.builder("com.zl.weilu.saber.viewmodel", typeSpec).build();
}

private void brewLiveData(String fieldName, TypeName valueTypeName, TypeSpec.Builder builder){

    String liveDataType;
    ClassName liveDataTypeClassName;

    liveDataType = "m$L = new MutableLiveData<>()";
    liveDataTypeClassName = ClassName.get("androidx.lifecycle", "MutableLiveData");

    ParameterizedTypeName typeName = ParameterizedTypeName.get(liveDataTypeClassName, valueTypeName);

    FieldSpec field = FieldSpec.builder(typeName, "m" + fieldName, Modifier.PRIVATE)
            .build();

    MethodSpec getMethod = MethodSpec
            .methodBuilder("get" + fieldName)
            .addModifiers(Modifier.PUBLIC)
            .returns(field.type)
            .beginControlFlow("if (m$L == null)", fieldName)
            .addStatement(liveDataType, fieldName)
            .endControlFlow()
            .addStatement("return m$L", fieldName)
            .build();

    MethodSpec getValue = MethodSpec
            .methodBuilder("get" + fieldName + "Value")
            .addModifiers(Modifier.PUBLIC)
            .returns(valueTypeName)
            .addStatement("return this.$N().getValue()", getMethod)
            .build();

    MethodSpec setMethod = MethodSpec
            .methodBuilder("set" + fieldName)
            .addModifiers(Modifier.PUBLIC)
            .returns(void.class)
            .addParameter(valueTypeName, "mValue")
            .beginControlFlow("if (this.m$L == null)", fieldName)
            .addStatement("return")
            .endControlFlow()
            .addStatement("this.m$L.setValue(mValue)", fieldName)
            .build();

    MethodSpec postMethod = MethodSpec
            .methodBuilder("post" + fieldName)
            .addModifiers(Modifier.PUBLIC)
            .returns(void.class)
            .addParameter(valueTypeName, "mValue")
            .beginControlFlow("if (this.m$L == null)", fieldName)
            .addStatement("return")
            .endControlFlow()
            .addStatement("this.m$L.postValue(mValue)", fieldName)
            .build();

    builder.addField(field)
            .addMethod(getMethod)
            .addMethod(getValue)
            .addMethod(setMethod)
            .addMethod(postMethod);

}

輸出文件:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   ...
    for (Map.Entry<String, ClassEntity> item : classEntityMap.entrySet()) {
        try {
            brewViewModel(item).writeTo(filer);
        } catch (Exception e) {
            messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), item.getValue().getElement());
        }
    }
    return true;
}

3.註冊處理器

註冊處理器才可以使處理器生效,使用Google開源的AutoService的庫。

dependencies {
    implementation 'com.squareup:javapoet:1.13.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
}

然後添加@AutoService註解即可。

@AutoService(Processor.class)
public class LiveDataProcessor extends AbstractProcessor {
   
}

4.調試註解處理器

項目的gradle.properties中配置:

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

接著Run -> Edit Configurations -> 點擊左上角加號 -> 選擇 Remote -> 指定module(可選)

註意兩個端口號一致。然後選擇添加的“APT”,點擊debug按鈕啟動。

後面就是打斷點,編譯項目即可。

5.支持增量編譯

Gradle 在 5.0 增加瞭對 Java 增量編譯的支持,通過增量編譯,我們能夠獲得一些優點:

  • 更少的編譯耗時
  • 更少的字節碼修改

如果註解處理器沒有支持增量編譯,那麼編譯時,會輸出以下日志:

w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.x.XXProcessor (NON_INCREMENTAL).

Gradle 支持兩種註解處理器的增量編譯:isolating 和 aggregating。

支持方法是在 META-INF/gradle/incremental.annotation.processors 文件中聲明支持增量編譯的註解處理器。

xx-complier/src/main/
├── java
│   ...
│   └── LiveDataProcessor
└── resources
    └── META-INF
        ├── gradle
        │   └── incremental.annotation.processors
        └── services
            └── javax.annotation.processing.Processor

incremental.annotation.processors內容如下:

com.zl.weilu.saber.compiler.LiveDataProcessor,aggregating

這部分詳細內容見 讓 Annotation Processor 支持增量編譯。

6.使用處理器

添加依賴:

dependencies {
    implementation project(':saber-annotation')
    annotationProcessor project(':saber-compiler')
}

首先創建一個類,使用@LiveData註解標記你要保存的數據。

public class SeekBar {

    @LiveData
    Integer value;
}

Build – > Make Project 生成代碼如下:

public class SeekBarViewModel extends ViewModel {
  private MutableLiveData<Integer> mValue;

  public MutableLiveData<Integer> getValue() {
    if (mValue == null) {
      mValue = new MutableLiveData<>();
    }
    return mValue;
  }

  public Integer getValueValue() {
    return getValue().getValue();
  }

  public void setValue(Integer mValue) {
    if (this.mValue == null) {
      return;
    }
    this.mValue.setValue(mValue);
  }

  public void postValue(Integer mValue) {
    if (this.mValue == null) {
      return;
    }
    this.mValue.postValue(mValue);
  }
}

至此,我們就完成瞭一個簡單的自定義處理器。

以上就是如何使用Android註解處理器的詳細內容,更多關於Android註解處理器的資料請關註WalkonNet其它相關文章!

推薦閱讀:

    None Found