自定义注解框架的那些事

一、前言

距离上次更新已过一个半月,工作太忙也不是停更的理由。我这方面做得很不好,希望大家给予监督。首先会讲解【编译期资源注入】,接着是【下拉刷新注入】(通过注解来实现下拉刷新功能),最后打造一款【特色的注解框架】。

大家准备好公交卡了吗,开车了 …

二、什么是注解

每位童鞋对 注解 都有自己的理解,字面上的意思就是【额外的加入】,在项目当中使用的注解的框架已经越来越多,如 : retrofit ,butterknifeandroidannotations … 2017年Android百大框架排行榜 基本都会看到注解的身影 。

注解可以减少大量重复的工作,提高开发效率,注解也非常的灵活,可以注解到 类、方法、属性等 当中,用来自动完成一些规律性的代码,以及降低类与类之间的耦合度。

前一篇文章对运行时RUNTIME事件的注解做了一个简单的讲解 初谈Android-Annotations(二) , 本篇主要简单讲解编译时CLASS**res**文件下的资源注入。前者一般是通过反射来实现的,影响效率,接下来一起来了解下编译时CLASS的实现。

三、编译时的注解

类似 butterknifeandroidannotationsarouter 使用的都是编译时的注解CLASS。这里以 butterknife 为例,来看一看以下注解:

/**
 * Bind a field to the specified string resource ID.
 * <pre><code>
 * {@literal @}BindString(R.string.username_error) String usernameErrorText;
 * </code></pre>
 */
@Retention(CLASS) @Target(FIELD) //编译时注解  针对属性(成员变量)注解
public @interface BindString {
  /** String resource ID to which the field will be bound. */
  @StringRes int value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如上定义了资源字符串的注解,那么怎么去实现字符串的注入的呢?

这里就不得不提到注解处理器 AbstractProcessor,注解处理器是 javac 的一个工具,它用来在编译时扫描和处理注解(Annotation)。以编译过的字节码作为输入,生成文件(一般是使用 javapoet 生成 .Java 文件)作为输出,生成的 Java 文件一样被 javac 编译。同样运行时注解 (RetentionPolicy.RUNTIME) 和源码注解 (RetentionPolicy.SOURCE) 也可以在注解处理器进行处理。

注意:通过注解处理器处理注解是不能修改已经存在的 Java 类。例如增删一些方法。

四、字符串资源的注入

颜色(color),数组,尺寸的注入与字符串的注入类似,这里以字符串的注入来讲解。

先看看最终的实现效果:

log的打印日志:

通过日志可以看出:前面两个属性并没有在注解中引用字符串的资源,最终的结果和引用 R.string.app_name 的结果一样,这样可以节省代码量(为懒人服务)。目前支持驼峰式命名与前缀m命名,规则由你改写,随性打造属于你自己的注入框架。

基础知识科普

如下所示,实现一个自定义注解处理器,至少重写四个方法,并且注册你的自定义Processor

  • @AutoService(Processor.class),谷歌提供的自动注册注解,为你生成注册Processor所需要的格式文件。请在当前库的 gradle 文件添加如下依赖:
dependencies {
    compile fileTree(dir: ‘libs‘, include: [‘*.jar‘])
    compile ‘com.google.auto.service:auto-service:1.0-rc3‘//添加
    compile ‘com.google.auto:auto-common:0.8‘//添加
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • init(ProcessingEnvironment processingEnv) 初始化处理器,一般在这里获取我们需要的工具类
  • getSupportedAnnotationTypes() 返回注解器所支持的注解类型集合
  • getSupportedSourceVersion 返回Java版本
  • process 当于每个处理器的主函数main(),处理注解的逻辑(扫描、检验,以及生成Java文件),如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们,反之则要求后续 Processor 处理它们
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {

    private Types typeUtils; //类型工具
    private Elements elementUtils;//元素工具
    private Filer filer; //文件管理器
    private Messager messager;//处理异常

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        typeUtils = processingEnv.getTypeUtils();//获取类的信息
        elementUtils = processingEnv.getElementUtils();//获取程序的元素 如 包 类 方法
        filer = processingEnv.getFiler();//生成java文件
        messager = processingEnv.getMessager();//处理错误日志
    }

     /**
     * @param annotations   请求处理的注解类型
     * @param roundEnv  有关当前和以前的信息环境
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    /**
     * @return 返回的是 注解类型的合法全称集合,如果没有这样的类型,则返回一个空集合
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<String>();
        annotations.add(BindString.class.getCanonicalName());
        return annotations;
    }

    /**
     *
     * @return 通常返回SourceVersion.latestSupported()
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}
  • 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
  • 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

首先,我们梳理一下注解处理器的处理逻辑:

  • 遍历备注解的元素
  • 检验元素是否符合要求
  • 获取输出类参数
  • 生成 java 文件
  • 错误处理

接着来看个简单示例获取被注解的元素名称,与注解值:

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // roundEnv.getElementsAnnotatedWith()返回使用给定注解类型的元素
        for (Element element : roundEnv.getElementsAnnotatedWith(BindString.class)) {
            System.out.println("----------------------");
            // 判断元素的类型为Class
            if (element.getKind() == ElementKind.FIELD) {
                // 显示转换元素类型
                VariableElement  variableElement= (VariableElement) element;
                // 输出元素名称
                System.out.println(""+variableElement.getSimpleName());
                // 输出注解属性值
                System.out.println(""+variableElement.getAnnotation(BindString.class).value());
            }
            System.out.println("----------------------");
        }
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

MainActivity 类:

    @BindString(R.string.app_name)
    String appName;
  • 1
  • 2
  • 1
  • 2

Gradle console 控制台输出如下:

看到这里,你一定会想,怎么样才能使 appName 与 2131099681建立联系呢?

如果不使用注解是这样来建立联系的:

    appName = getResources().getString(2131099681); // R.string.app_name =  2131099681
  • 1
  • 1

说一万道一千,实现这行代码的自动注入是我们的最终目的。

最后,我们来理解一下 Element 的概念,因为它是我们获取注解的基础。

Processor 处理过程中,会扫描全部的 Java 源码,代码的每一个部分都是一个特定类型的 Element ,它们像是XML一层的层级机构,比如类、变量、方法等,每个Element代表一个静态的、语言级别的构件。

Element 有五个直接子接口,它们分别代表一种特定类型的元素,如下:

  • PackageElement 表示一个包程序元素
  • TypeElement 表示一个类,接口或枚举程序元素 类型
  • VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 属性
  • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序方法(静态或实例),包括注解类型元素 方法
  • TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数 如 public class MainActivity<T> 泛型 T

在开发中Element可根据实际情况强转为以上5种中的一种,它们都带有各自独有的方法,来看个简单的例子:

package com.github.wsannotation; // PackageElement

import java.util.List;

/**
 * desc:
 * author: wens
 * date: 2017/8/11.
 */

public class UserBean      // TypeElement
        <T extends List> {  // TypeParameterElement

    private int age;           // VariableElement
    private String name;        // VariableElement

    public UserBean() {          // ExecutableElement
    }

    public void setName(String name) {  // ExecutableElement
       String weight;               // VariableElement
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

其中 Element 代表的是源代码,而 TypeElement代表的是源代码中的类型元素,例如类。然而,TypeElement并不包含类本身的信息。你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror

相关文章链接:

自定义注解之编译时注解(RetentionPolicy.CLASS)(三)—— 常用接口介绍

字符串注入流程

这里参考了 ButterKnife 结合 androidannotations 的实现方式:

1、对元素效验 verify 系列方法

2、获取注解字段名和注解的资源ID以及处理资源ID为 -1 的情况

处理资源ID为 -1 的情况,我这里借鉴了 androidannotations 的处理方式:

        //处理默认情况
        if (resId == -1) {
            TypeElement androidRType = elementUtils.getTypeElement("com.github.butterknifelib.R.string");
            List<? extends Element> idEnclosedElements = androidRType.getEnclosedElements();
            List<VariableElement> idFields = ElementFilter.fieldsIn(idEnclosedElements);
            for (VariableElement idField : idFields) {
                TypeKind fieldType = idField.asType().getKind();
                if (fieldType.isPrimitive() && fieldType.equals(TypeKind.INT)) {
                //制定规则
                    if (idField.getSimpleName().toString().toLowerCase().replaceAll("_", "")
                            .equals(name.startsWith("m") ? name.substring(1, name.length()).toLowerCase() : name.toLowerCase())) {
                        resId = (int) idField.getConstantValue();
                        break;
                    }
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

更好的方式是以 map 键值对来存储资源类 。后期我会做详细讲解。

3、生成 xxxx$$ViewBinder.java (xxxx 代码 Activity、View、Dialog)文件

4、给生成 xxxx$$ViewBinder.java 添加资源信息

5、通过 ButterKnife.bind(this); 反射得到 xxxx$$ViewBinder.java 类,并调用注入资源的方法

时间: 2024-08-25 16:58:21

自定义注解框架的那些事的相关文章

Android 自定义注解框架

前言 在我们的项目中,我们几乎天天和一些固定的代码打交道,比如在Activity中你要写findViewById(int)方法来找到控件,然而这样子的代码对于一个稍微有点资格的程序员来说,都是毫无营养的,你根本学不到任何的东西,但是你却必须写.这也就是注解框架的出现,极大的简化了程序员的工作,并且让代码简洁.也许你早就使用过了注解的框架,那么你会自己自己写么?好了,今天就让大家来完成一个注解的框架 阅读的你需要掌握的知识 1.Java反射的知识 2.Java注解的知识 普通的写法 xml布局文件

XML序列化与反序列化+自定义XML注解框架XmlUtils

背景 前面一篇总结了Serializable的序列化与反序列化,现在接着总结XML.主要内容:XML基本的序列化与反序列化方法.一些注意事项.以及自定义了一个XML注解框架(简洁代码,解放双手). XML的序列化与反序列化 先与Serializable进行简单的对比: Serializable存储的文件,打开后无法正常查看,安全性高.xml文件可通过文本编辑器查看与编辑,可读性高(浏览器会格式化xml文件,更方便查看),安全性低: Serializable文件通过了签名,只能在自己的程序中反序列

SpringMVC验证框架Validation自定义注解实现传递参数到国际化资源文件

关于SpringMVC验证框架Validation的使用方法,不是本篇的重点,可参见博文SpringMVC介绍之Validation 在使用Validation时,一定有朋友遇到过一个问题,那就是:无法传递参数到国际化资源文件properties错误描述中. 举个例子: User类中 @NotEmpty(message="{password.empty.error}") private String password; 资源文件validation_zh_CN.properties中为

Java注解(自定义注解、view注入)

注解这东西虽然在jdk1.5就加进来了,但他的存在还是因为使用Afinal框架的view注入才知道的.一直觉得注入特神奇,加了一句就可以把对应view生成了. 下面我们来认识一下注解这个东西 一.注解相关知识 注解相当于一种标记,在javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事.标记可以加在包,类,字段,方法,方法的参数以及局部变量上. 1.元注解:作用是负责注解其他注解.Java5.0定义了4个标准的meta-annotati

转载:自定义注解

转载:https://juejin.im/entry/577142c3a633bd006435eea4 什么是注解 先来看看Java文档中的定义 An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct eff

springAOP自定义注解讲解

注解: 可以看作是对 一个 类/方法 的一个扩展的模版,每个 类/方法 按照注解类中的规则,来 为 类/方法 注解不同的参数,在用到的地方可以得到不同的 类/方法 中注解的各种参数 与值. 注解的原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动 态代理类.而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象 $Proxy1.通过代理对象调用自定义注解(接口)的方法,会最终调用 AnnotationInvocationHandler的i

注解框架---AndroidAnnotations

AndroidAnnotations是一个开源框架,旨在加快Android开发的效率.通过使用它开放出来的注解api,你几乎可以使用在任何地方, 大大的减少了无关痛痒的代码量,让开发者能够抽身其外,有足够的时间精力关注在真正的业务逻辑上面.而且通过简洁你的代码,也提高了代码的稳定性和后期的维护成本.以下AndroidAnnotations简称为AA 可能会有人提出异议了,我们移动设备的性能,不比后台服务器拥有充足的内存和运算能力.当大量的使用注解的时候,会不会对APP的造成什么不良的影响,会不会

自定义注解的简单使用

    框架开发时不免会涉及到配置文件,如properties.xml以及txt等格式的文件.这里介绍框架中通过自定义注解的形式简化配置: 根据需求编写自定义注解中的属性(这里以JDBCConfig为例,这是一个注入数据库常用配置的注解类) @Target是java的元注解(即修饰注解的注解),这里的@Target({METHOD,TYPE})指可以修饰方法.描述类.接口(包括注解类型) 或enum声明. @Retention是java中的运行时注解,可以划分为三类   1.RetentionP

Java自定义注解的使用

最近学习了一下Java的自定义注解,终于知道了框架那些注解是咋个写出来的了,以后我也可以自己写框架,自己定义注解,听着是不是很牛的样子?不要心动,赶快行动,代码很简单,一起来学习一下吧! 这个例子是模仿框架的一个对sql拼装的例子,用注解实现对model也就是实体类的注释,就可以写出查询该字段的sql.好了,废话少说,一看代码便知.大家可以根据自己的需求修改. package com.annotation.demo; import java.lang.annotation.Documented;