Java编译注解-自动生成代码

Android

Annotation注解在Android的开发中的使用越来越普遍,例如EventBus、ButterKnife、Dagger2等,记一次使用插件annotationProcessor实现下载监听注解框架,为什么不用android-apt呢,我不会告诉你android-apt不再维护了。


Javadoc代码注解-代码文档:传送门


注解简介

注解

Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记。

定义注解用的关键字是@interface

注解的用处

  • 生成文档,编译进行检查。这是最常见的,即Javadoc代码注解
  • 跟踪代码依赖性,实现替代配置文件功能。如EventBus、ButterKnife、Dagger2依赖注入等,本篇记录一次编译注解使用过程

元注解

java.lang.annotation提供了四种元注解,专门注解其他的注解:

  • @Documented –注解是否将包含在Javadoc文档中

  • @Retention –什么时候使用该注解,有三种选择,默认为CLASS

    • RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
    • RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
    • RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
  • @Target –表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括

    • ElementType.CONSTRUCTOR:用于描述构造器
    • ElementType.FIELD:成员变量、对象、属性(包括enum实例)
    • ElementType.LOCAL_VARIABLE:用于描述局部变量
    • ElementType.METHOD:用于描述方法
    • ElementType.PACKAGE:用于描述包
    • ElementType.PARAMETER:用于描述参数
    • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Inherited – 是否允许子类继承该注解,默认为false

Android中使用编译时注解,自动生成代码

这里只讲解编译注解,其他注解待续~~~


Downloader源码传送门


Annotations项目结构

  • DownloadAnnotations:Java工程,申明使用的注解
  • DownloadCompiler:Java工程,用于编译期间自动生成代码
  • DownloaderLibrary:下载依赖库
  • Demo:测试示例

创建自定义注解

创建Java工程:DownloadAnnotations

  • 声明一个Download注解,声明周期为Class,作用域为方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
    public @interface Download
    {
    /**
    * 下载开始,获取文件大小
    * @see com.excellence.downloader.FileDownloader.DownloadTask#getFileSize()
    *
    */
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
    @interface onPreExecute
    {
    String[] value() default { NO_URL };
    }
    }
  • DownloadAnnotations的build.gradle配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    apply plugin: 'java'

    dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    }

    tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
    }

    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7

解析编译时注解

创建Java工程:DownloadCompiler

解析编译时注解需要继承AbstractProcessor,并且使用注解@AutoService(Processor.class),该注解在编译时自动执行被注解的类,即自动执行该类的Java程序,用于创建类文件。

  • DownloadProcessor事件注解扫描器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    @AutoService(Processor.class)
    public class DownloadProcessor extends AbstractProcessor
    {
    private ElementHandler mElementHandler = null;

    /**
    * 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的{@link #init}方法,它会被注解处理工具调用,并输入{@link ProcessingEnvironment}参数。{@link ProcessingEnvironment}提供很多有用的工具类{@link Elements}、{@link Types}、{@link Filer}。
    *
    * @param processingEnvironment
    */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment)
    {
    super.init(processingEnvironment);
    mElementHandler = new ElementHandler(processingEnvironment.getFiler(), processingEnvironment.getElementUtils());
    }

    /**
    * 必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。
    *
    * @return
    */
    @Override
    public Set<String> getSupportedAnnotationTypes()
    {
    Set<String> annotations = new LinkedHashSet<>();
    annotations.add(Download.onPreExecute.class.getCanonicalName());
    return annotations;
    }

    /**
    * 用来指定你使用的Java版本。
    *
    * @return
    */
    @Override
    public SourceVersion getSupportedSourceVersion()
    {
    return SourceVersion.latestSupported();
    }

    /**
    * 这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数{@link RoundEnvironment},可以让查询出包含特定注解的被注解元素。
    * 该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理
    *
    * @param set
    * @param roundEnv
    * @return
    */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)
    {
    mElementHandler.clean();
    mElementHandler.handleDownload(roundEnv);
    mElementHandler.createProxyFile();
    return true;
    }
    }
  • ElementHandler元素处理(代码过长,请查看源码)

    • 编译时自动创建事件代理的类文件的管理类,用于管理有注册代理的类,生成路径:Demo/build/generated/source/apt/debug/com/excellence/downloader/ProxyClassCounter
      ProxyClassCounter

    • 编译时自动创建事件代理的类文件,用于发送监听接口,生成类文件的路径:Demo/build/generated/source/apt/debug/com/zv/downloader/downloader/SingleThreadActivity$$DownloadListenerProxy
      SingleThreadActivity$$DownloadListenerProxy

  • DownloadCompiler的build.gradle配置

    • com.google.auto.service:auto-service:1.0-rc2 谷歌提供的Java 生成源代码库
    • com.squareup:javapoet:1.9.0 提供了各种 API 让你用各种姿势去生成 Java 代码文件,javapoet传送门
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      apply plugin: 'java'

      dependencies {
      compile fileTree(dir: 'libs', include: ['*.jar'])
      compile 'com.google.auto.service:auto-service:1.0-rc2'
      compile 'com.squareup:javapoet:1.9.0'
      compile project(':DownloadAnnotations')
      }

      tasks.withType(JavaCompile) {
      options.encoding = "UTF-8"
      }

      sourceCompatibility = JavaVersion.VERSION_1_7
      targetCompatibility = JavaVersion.VERSION_1_7

到此注解生成代码过程结束

Downloader依赖库处理注解

  • 根据DownloadCompile里的ElementHandler类创建注解接口ISchedulerListener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface ISchedulerListener<TASK>
    {
    void onPreExecute(TASK task);

    void onProgressChange(TASK task);

    void onProgressSpeedChange(TASK task);

    void onCancel(TASK task);

    void onError(TASK task);

    void onSuccess(TASK task);
    }
  • DownloadScheduler事件调度器,注册、解绑、监听,用于处理任务状态的调度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 初始化代理参数,获取注解类集合
    */
    private void initDownloadCounter()
    {
    try
    {
    Class clazz = Class.forName("com.excellence.downloader.ProxyClassCounter");
    Method download = clazz.getMethod("getDownloadCounter");
    Object object = clazz.newInstance();
    Object downloadCounter = download.invoke(object);
    if (downloadCounter != null)
    mDownloadCounter = unmodifiableSet((Set<String>) downloadCounter);
    }
    catch (ClassNotFoundException e)
    {
    e.printStackTrace();
    }
    catch (NoSuchMethodException e)
    {
    e.printStackTrace();
    }
    catch (InstantiationException e)
    {
    e.printStackTrace();
    }
    catch (IllegalAccessException e)
    {
    e.printStackTrace();
    }
    catch (InvocationTargetException e)
    {
    e.printStackTrace();
    }
    }

Demo使用注解监听

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册
Downloader.register(this);

// 解绑
Downloader.unregister(this);

// 监听
@Download.onPreExecute
public void onPre(DownloadTask task)
{
/**
* 注解参数不添加URL,则获取全部任务的下载监听;
* 加了URL,则过滤出对应的任务的下载监听
* 如:@Download.onPreExecute({URL1, URL2})
*/
}

感谢

谢谢老板,请尽情用红包来蹂躏我吧!!!
0%