Android 自定义注解之编译时注解(RetenttionPolicy.CLASS)

简介: 注解处理器(Annotation Processor)注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。

注解处理器(Annotation Processor)

注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

1. 新建两个Module

File->New->New Module...->Java Library,填写好Library name和Java class name后点击完成。
注意,这里必须为Java库,不然会找不到javax包下的相关资源。


img_e74f5f4cc63e5899e03c5ce81143cb1f.png
图1.png

其中annations是存放注解的,processors是存放注解处理器的。

2. 新建编译时注解

/**
 * 编译时注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface CustomAnnation {
    String value();
}

3. 定义注解处理器

定义一个注解处理器 CustomProcessor ,每一个处理器都是继承于AbstractProcessor,并要求必须复写 process() 方法,通常我们使用复写以下4个方法:

/**
 * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行
 */
public class CustomProcessor extends AbstractProcessor{
    /**
     * init()方法会被注解处理器工具调用,并输入ProcessingEnvironment参数。
     * ProcessingEnvironment 提供很多有用的工具类Elements,Types和Filter
     * @param processingEnvironment 提供给process用来访问工具框架的环境
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    /**
     * 相当于每个处理器的主函数main(),在这里写扫描、评估和处理注解的代码,以及生成java文件。
     * 输入参数RoundEnvironment可以查询出包含特定注解的被注解元素
     * @param set 请求处理注解类型
     * @param roundEnvironment 有关当前和以前的信息环境
     * @return 返回true,则这些注解已声明并且不要求后续Processor处理他们;
     *          返回false,则这些注解未声明并且可能要求后续Processor处理他们;
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }

    /**
     * 这里必须指定,这个注解处理器是注册给那个注解的。
     * 注意:它的返回值是一个字符串的集合,包含本处理器想要处理注解的注解类型的合法全程。
     * @return 注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合。
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(CustomAnnation.class.getCanonicalName());
        return annotations;
    }

    /**
     * 指定Java版本,通常这里使用SourceVersion.latestSupported(),
     * 默认返回SourceVersion.RELEASE_6
     * @return 使用的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

也可以使用注解的方式来指定Java版本和注解类型


img_d4dee54ae1c80c80d646e48268921535.png
图2.png

4. 添加注解的处理

    /**
     * 相当于每个处理器的主函数main(),在这里写扫描、评估和处理注解的代码,以及生成java文件。
     * 输入参数RoundEnvironment可以查询出包含特定注解的被注解元素
     * @param set 请求处理注解类型
     * @param roundEnvironment 有关当前和以前的信息环境
     * @return 返回true,则这些注解已声明并且不要求后续Processor处理他们;
     *          返回false,则这些注解未声明并且可能要求后续Processor处理他们;
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)返回使用给定的注解类型的元素
        for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
            System.out.println("----------------------------------");
            // 判断元素的类型为Class
            if (element.getKind() == ElementKind.CLASS) {
                // 显示转换元素类型
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
                System.out.println(typeElement.getSimpleName());
                // 输出注解属性值
                System.out.println(typeElement.getAnnotation(CustomAnnotation.class).value());
            }
            System.out.println("----------------------------------");
        }
        return false;
    }

5. 注解处理器配置

1、在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;

项目结构:


img_427483ca3597de3197e343d4e9234eab.png
图3.png

6. 使用

主Module依赖annotations和processors两个moudle,然后编译,如果未打印出结果,则Build->ReBuild或者Build->Clean Project->Make Project.

@CustomAnnotation("Hello World")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

7. 打印结果

img_535fd96ae09b94ca21e3fde6423a3c53.png
图4.png

8. 存在的问题

我们的主项目中引用了 processors 库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主项目添加了这个库会引入很多不必要的文件,为了处理这个问题我们需要引入个插件android-apt,它能很好地处理这个问题。

9. AutoService

AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,你只需要在你定义的注解处理器上添加 @AutoService(Processor.class) 就可以了,简直不能再方便了。

  • 依赖AutoService库,在processors的build.gradle文件中添加一下依赖,并同步工程。
    compile 'com.google.auto.service:auto-service:1.0-rc2'
  • 使用


    img_b26d533fd3af0fca7b6d0fea55499daa.png
    图5.png

10. 问题

这时重新Make下工程也能看到同样的输出信息了。但是如果你编译生成APK时,可能会出现文件重复的问题。解决办法是在主项目的 build.gradle 加上这么一段:

  packagingOptions {  
     exclude 'META-INF/services/javax.annotation.processing.Processor'  
  }  

这样就不会报错了,这是其中的一个解决方法,还有个更好的解决方法就是用上面提到的android-apt

11. Android-apt

官网有这么一段描述:

The android-apt plugin assists in working with annotation processors in combination with Android Studio. It has two purposes:
1、Allow to configure a compile time only annotation processor as a dependency, not including the artifact in the final APK or library
2、Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio

大体来讲它有两个作用:
能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件

12. 使用

// 主工程的build.gradle
buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:1.3.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
// 主Module的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'


dependencies {
// ...
    compile project(':annotations')
    apt project(':processors')
//    compile project(':processors')
//    compile project(':processors1')
}

13. JavaPoet

在使用编译时注解时,需要在编译期间对注解进行处理,在这里我们没办法影响程序的运行逻辑,但我们可以进行一些需处理,比如生成一些功能性代码来辅助程序的开发,最常见的是生成.java 源文件,并在程序中可以调用到生成的文件。这样我们就可以用注解来帮助我们处理一些固定逻辑的重复性代码(如butterknife),提高开发的效率。

通过注解处理器来生成 .java 源文件基本上都会使用javapoet 这个库,JavaPoet一个是用于产生 .java 源文件的辅助库,它可以很方便地帮助我们生成需要的.java 源文件,下面来看下具体使用方法。
使用如下代码进行依赖:

    compile "com.squareup:javapoet:1.9.0"

14. 注解生成器

/**
 * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.mazaiting.CustomAnnotation")
public class CustomProcessor extends AbstractProcessor{

    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        // Filter是个接口,至此吃通过注解处理器创建新文件
        mFiler = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
            // 判断元素的类型为Class
            if (element.getKind() == ElementKind.CLASS) {
                // 显示转换元素类型
                TypeElement typeElement = (TypeElement) element;
                // 输出元素名称
//                System.out.println(typeElement.getSimpleName());
//                // 输出注解属性值
//                System.out.println(typeElement.getAnnotation(CustomAnnotation.class).value());
                // 创建main方法
                MethodSpec main =
                        MethodSpec.methodBuilder("main")
                                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                                .returns(void.class)
                                .addParameter(String[].class, "args")
                                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                                .build();
                String value = element.getAnnotation(CustomAnnotation.class).value();
                String first = value.substring(0, 1);
                String className = first.toUpperCase() + value.substring(1, value.length());
                TypeSpec valueClass =
                        TypeSpec.classBuilder(className)
                                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                                .addMethod(main)
                                .build();
                // 生成文件
                try {
                    // 生成com.mazaiting.xxx.java
                    JavaFile javaFile =
                            JavaFile.builder("com.mazaiting.example", valueClass)
                                    .addFileComment("This codes are generated automatically. Do not modify!")
                                    .build();
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
}

15. MainActivity使用

@CustomAnnotation("HelloWorld")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

16. 生成Java类

生成的Java类结构


img_472ca3e07cb9e8d29237cdc35cd7b7cd.png
图6.png
目录
相关文章
|
2月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
41 3
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
87 0
|
4月前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
4月前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
285 1
|
22天前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
23天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
30 5
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
93 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
编译器 Android开发
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
|
2月前
|
Ubuntu Shell API
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a