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

如何使用Android注解处理器的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部