如何使用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注解处理器的资料请关注我们其它相关文章!

(0)

相关推荐

  • 3分钟纯 Java 注解搭个管理系统的示例代码

    最近接触到个新项目,发现它用了一个比较有意思的框架,可以说实现了我刚入行时候的梦想,所以这里马不停蹄的和大家分享下. 在我刚开始工作接触的项目都还没做前后端分离,经常需要后端来维护页面,有时候觉得自己好像天生不适合干前端,你要是让我研究研究后端的技术,看个中间件源码啊,分析分析什么框架底层原理啊,这都问题不大,偶尔搞一下JS也可以.你要是让我写个css样式,那简直要命了,一点也提不起兴趣,不知道有没有跟我一样的. 今天要介绍的框架直接不用写页面了,话不多说,下边咱们直奔主题 Erupt一个通用后

  • Java注解处理器简单实例

    如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.JavaSE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器. 注解处理器类库(java.lang.reflect.AnnotatedElement): Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口.除此之外,Java在java.lang.reflect包下新增了AnnotatedElement

  • 解决golang gin框架跨域及注解的问题

    在golang的路上缓慢前进 Gin框架 跨域问题的解说与方法 代码如下: package main import ( "github.com/gin-gonic/gin" "awesomeProject/app/app_routers" "strings" "fmt" "net/http" ) /* 路由初始化*/ var ( engine = gin.Default() ) func main() {

  • Java注解处理器学习之编译时处理的注解详析

    1. 一些基本概念 在开始之前,我们需要声明一件重要的事情是:我们不是在讨论在运行时通过反射机制运行处理的注解,而是在讨论在编译时处理的注解. 编译时注解跟运行时注解到底区别在什么地方?其实说大也不大,主要是考虑到性能上面的问题.运行时注解主要是完全依赖于反射,反射的效率比原生的慢,所以在内存比较少,CPU比较烂的机器上会有一些卡顿现象出现.而编译时注解完全不会有这个问题,因为它在我们编译过程(java->class)中,通过一些注解标示,去动态生成一些类或者文件,所以跟我们的APK运行完全没有

  • Java 自定义注解的魅力

    注解是什么? ①.引用自维基百科的内容: Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法 元数据 . Java语言中的类.方法.变量.参数和包等都可以被标注.和Javadoc不同,Java标注可以通过反射获取标注内容.在编译器生成类文件时,标注可以被嵌入到字节码中.Java虚拟机可以保留标注内容,在运行时可以获取到标注内容. 当然它也支持自定义Java标注. ②.引用自网络的内容: Java 注解是在 JDK5 时引入的新特性,注解(也被称为 元数据 )为我们在代码

  • MybatisPlus 不修改全局策略和字段注解如何将字段更新为null

    mybatis-plus 以下简称mp,目前应该也算是主流的一款数据访问层应用框架.源于其对mybatis 的近乎完美的封装,让我们在使用的时候无比的顺滑, 几乎提供了所有单表操作的方法,大大提升了效率.并且这款框架还是国产的哦,没了解过的可以去了解一下. 回归正题,我们这次来讲一下,怎么样通过mp将数据库中的一个字段更新为null. 可能很多人会觉得奇怪,更新为null, 直接set field = null 不就可以了.这里大家要注意一下,一般情况,我们在使用mp的时候,他的默认策略是空不更

  • Android kotlin使用注解实现防按钮连点功能的示例

    SingleClick: @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class SingleClick( // 点击间隔时间,毫秒 val value: Long = 500 ) SingleClickAspect: import android.os.SystemClock import org.aspectj.lang.ProceedingJoinPoint im

  • 如何使用Android注解处理器

    我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器. 注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息. 本篇用我之前写的Saber举例说明. 1.定义注解 推荐New -> Module -> Java Library,新建一个Java Library Module,命名为xx-annotation.用来单独存放注解. 既然是注解处理器,那么首先需要有注解.自定义一个注解使用@interface关键字. public @inte

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • Android注解基础介绍快速入门与解读

    首先什么是注解?@Override就是注解,它的作用是: 1.检查是否正确的重写了父类中的方法. 2.标明代码,这是一个重写的方法. 1.体现在于:检查子类重写的方法名与参数类型是否正确:检查方法private/final/static等不能被重写.实际上@Override对于应用程序并没有实际影响,从它的源码中可以出来. 2.主要是表现出代码的可读性. 作为Android开发中熟知的注解,Override只是注解的一种体现,更多时候,注解还有以下作用: 降低项目的耦合度. 自动完成一些规律性的

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • 自定义Android注解系列教程之注解变量

    前言 对于Android注解,或多或少都有一点接触,但相信大多数人都是在使用其它依赖库的时候接触的.因为有些库如果你想使用它就必须使用它所提供的注解.例如:ButterKnife.Dagger2.Room等等. 至于为何使用注解?使用过的应该都知道,最明显的就是方便.简洁.通过使用注解可以在项目编译阶段,帮助我们自动生成一些重复的代码,减轻我们的负担.典型的ButterKnife本质就是使用Android注解,通过注解来减少我们对view.findViewById的编写,提高我们的开发效率.上一

  • Android注解使用之ButterKnife 8.0详解

    前言: App项目开发大部分时候还是以UI页面为主,这时我们需要调用大量的findViewById以及setOnClickListener等代码,控件的少的时候我们还能接受,控件多起来有时候就会有一种想砸键盘的冲动.所以这个时候我们想着可以借助注解的方式让我们从这种繁重的工作中脱离出来,也让代码变得更加简洁,便于维护,今天主要学习一下只专注View.Resource.Action注解框架ButterKnife. ButterKnife介绍 ButterKnife是一个专注于Android系统的V

  • 手把手教你实现Android编译期注解

    详细阐述了实现一个Android编译期注解sdk的步骤以及注意事项,并简要分析了运行时注解以及字节码技术在生成代码上与编译期注解的不同与优劣 一.编译期注解在开发中的重要性 从早期令人惊艳的ButterKnife,到后来的以ARouter为首的各种路由框架,再到现在谷歌大力推行的Jetpack组件,越来越多的第三方框架都在使用编译期注解这门技术,可以说不管你是想要深入研究这些第三方框架的原理 还是要成为一个Android高级开发工程师,编译期注解都是你不得不好好掌握的一门基础技术. 本文从基础的

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

随机推荐