Android 注解处理器使用攻略

Android 注解处理器使用攻略,第1张

概述上一篇写了JavaPoet使用攻略,了解了JavaPoet用法。那么我们就可以结合今天的AnnotationProcessingTool(APT)来自定义注解处理器。注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。本篇用我之前写的Saber举例说明。1.定义注解推荐New->Module->Java

上一篇写了JavaPoet使用攻略,了解了JavaPoet用法。那么我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器。

注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。

本篇用我之前写的Saber举例说明。

1.定义注解

推荐New -> Module -> Java library,新建一个Java library Module,命名为xx-annotation。用来单独存放注解。

既然是注解处理器,那么首先需要有注解。自定义一个注解使用@interface关键字。

public @interface liveData {}

然后我们需要用到注解的注解,也就是元注解来控制注解的行为。这里我简单介绍一些元注解。

Retention 表示注解的保留范围。值用RetentionPolicy枚举类型表示,分为CLASSRUNTIMESOURCETarget 表示注解的使用范围。值用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的类型。

ElementTypeElementKindElement
TYPECLASSTypeElement
FIELDFIELDVariableElement
METHODMETHODExecutableElement

下面是封装的简易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=trueorg.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);  }}

至此,我们就完成了一个简单的自定义处理器。

7.参考

注解处理器(APT)了解一下

详尽的Android编译时注解处理器教程

让 Annotation Processor 支持增量编译

总结

以上是内存溢出为你收集整理的Android 注解处理器使用攻略全部内容,希望文章能够帮你解决Android 注解处理器使用攻略所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1046684.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存