返回顶部
首页 > 资讯 > 移动开发 >如何使用Android注解处理器
  • 356
分享到

如何使用Android注解处理器

2024-04-02 19:04:59 356人浏览 泡泡鱼
摘要

我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器。 注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。 本篇用我之前

我们就可以结合今天的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注解处理器的资料请关注编程网其它相关文章!

--结束END--

本文标题: 如何使用Android注解处理器

本文链接: https://lsjlt.com/news/123405.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何使用Android注解处理器
    我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器。 注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。 本篇用我之前...
    99+
    2024-04-02
  • 怎么使用Android注解处理器
    小编给大家分享一下怎么使用Android注解处理器,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1.定义注解推荐New -> Module -> Ja...
    99+
    2023-06-14
  • 如何在Android中使用 AOP注解
    今天就跟大家聊聊有关如何在Android中使用 AOP注解,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、简介在Android 里面 注解主要用来干这么几件事:和编译器一起给你一些...
    99+
    2023-05-31
    android aop 注解
  • Android AOP之注解处理解释器详解(二)
    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android ...
    99+
    2022-06-06
    注解 解释器 aop Android
  • Android AOP中注解处理解释器的作用有哪些
    本篇文章给大家分享的是有关Android AOP中注解处理解释器的作用有哪些,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一、提取Annotation信息当开发者使用了Anno...
    99+
    2023-05-31
    android aop 注解
  • Android 注解解析及使用
    目录 一、注解解析。 1.什么是注解? 2.为什么要使用注解? 3.android中常见的注解有哪些? 4.元注解。 二、注解使用。 1.如何实现一个注解? 2.android...
    99+
    2022-06-06
    注解 Android
  • 如何使用webservice自定义注解处理参数加解密问题
    小编给大家分享一下如何使用webservice自定义注解处理参数加解密问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!webservice自定义注解处理参数加解...
    99+
    2023-06-22
  • 如何理解Java注解和注解解析器
    这篇文章主要讲解了“如何理解Java注解和注解解析器”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解Java注解和注解解析器”吧!什么是元数据(met...
    99+
    2024-04-02
  • Spring使用注解方式处理事务
    Spring有专门的类来处理事务,在这之前我们先要理解Spring处理事务中的几个概念: 1.接口: 事务管理器是PlatformTransactionManager接口,在接口中定...
    99+
    2024-04-02
  • CSS预处理器如何使用
    这篇文章给大家分享的是有关CSS预处理器如何使用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。   我们先来看一下什么是CSS预处理器?   默认的CSS是到现在为止不可能实...
    99+
    2024-04-02
  • 如何使用RequiredArgsConstructor注解
    这篇文章主要讲解了“如何使用RequiredArgsConstructor注解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用RequiredArgsConstructor注解”吧!爆...
    99+
    2023-06-16
  • @Query注解如何使用
    这篇文章主要讲解了“@Query注解如何使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“@Query注解如何使用”吧!@Query注解的用法1.一个使用@Query注解的简单例子@Quer...
    99+
    2023-06-29
  • @AliasFor注解如何使用
    本篇内容主要讲解“@AliasFor注解如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“@AliasFor注解如何使用”吧!一、前言@AliasFor注解基本上都是在spring源码当中出...
    99+
    2023-07-05
  • 如何使用注解配置Spring容器
    这篇文章给大家分享的是有关如何使用注解配置Spring容器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体如下:@Configuration标注在类上,相当于将该类作为spring的xml的标签@Configu...
    99+
    2023-05-30
    spring
  • MyBatisPlus如何使用@TableField注解处理默认填充时间的问题
    这篇文章给大家分享的是有关MyBatisPlus如何使用@TableField注解处理默认填充时间的问题的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。使用@TableField注解处理默认填充时间的情况在数据库的...
    99+
    2023-06-26
  • APT 注解处理器实现 Lombok 常用注解功能详解
    目录1 背景2 生成字节码原理2.1 APT(Annotation Processing Tool )注解处理器2.2 AbstractProcessor 注解处理器的使用...
    99+
    2024-04-02
  • Android 如何使用log4j及注意事项
     Android 使用log4j 前言:  如果要直接在android工程中使用log4j,是有点问题的,会报如下的错: 11-23 09:44:56...
    99+
    2022-06-06
    log4j log Android
  • 如何正确使用注解
    本篇内容介绍了“如何正确使用注解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!日志脱敏场景简介在日志里我们的日志一般打印的是 model 的...
    99+
    2023-06-16
  • Java5.0中注解如何使用
    这篇文章将为大家详细讲解有关Java5.0中注解如何使用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。元注解Meta-annotation元注解的作用就是负责注解其他注解。Java5.0定义...
    99+
    2023-06-20
  • Spring @ComponentScan注解如何使用
    今天小编给大家分享一下Spring @ComponentScan注解如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作