自定义注解框架的那些事
来源:互联网 发布:淘宝手机直通车怎么找 编辑:程序博客网 时间:2024/06/08 03:56
一、前言
距离上次更新已过一个半月,工作太忙也不是停更的理由。我这方面做得很不好,希望大家给予监督。首先会讲解【编译期资源注入】,接着是【下拉刷新注入】(通过注解来实现下拉刷新功能),最后打造一款【特色的注解框架】。
大家准备好公交卡了吗,开车了 …
二、什么是注解
每位童鞋对 注解 都有自己的理解,字面上的意思就是【额外的加入】,在项目当中使用的注解的框架已经越来越多,如 : retrofit ,butterknife,androidannotations … 2017年Android百大框架排行榜 基本都会看到注解的身影 。
注解可以减少大量重复的工作,提高开发效率,注解也非常的灵活,可以注解到 类、方法、属性等 当中,用来自动完成一些规律性的代码,以及降低类与类之间的耦合度。
前一篇文章对运行时RUNTIME事件的注解做了一个简单的讲解 初谈Android-Annotations(二) , 本篇主要简单讲解编译时CLASS**res**文件下的资源注入。前者一般是通过反射来实现的,影响效率,接下来一起来了解下编译时CLASS的实现。
三、编译时的注解
类似 butterknife,androidannotations,arouter 使用的都是编译时的注解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();}
如上定义了资源字符串的注解,那么怎么去实现字符串的注入的呢?
这里就不得不提到注解处理器 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'//添加}
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(); }}
首先,我们梳理一下注解处理器的处理逻辑:
遍历备注解的元素
检验元素是否符合要求
获取输出类参数
生成 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; }
MainActivity 类:
@BindString(R.string.app_name) String appName;
Gradle console 控制台输出如下:
看到这里,你一定会想,怎么样才能使 appName
与 2131099681
建立联系呢?
如果不使用注解是这样来建立联系的:
appName = getResources().getString(2131099681); // R.string.app_name = 2131099681
说一万道一千,实现这行代码的自动注入是我们的最终目的。
最后,我们来理解一下 Element
的概念,因为它是我们获取注解的基础。
Processor
处理过程中,会扫描全部的 Java 源码,代码的每一个部分都是一个特定类型的 Element
,它们像是XML
一层的层级机构,比如类、变量、方法等,每个Element
代表一个静态的、语言级别的构件。
Element
有五个直接子接口,它们分别代表一种特定类型的元素,如下:
PackageElement 表示一个包程序元素
TypeElement 表示一个类,接口或枚举程序元素 类型
VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 属性
ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序方法(静态或实例),包括注解类型元素 方法
TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数 如
public class MainActivity<T>
泛型 T
在开发中Element
可根据实际情况强转为以上5种中的一种,它们都带有各自独有的方法,来看个简单的例子:
package com.github.wsannotation; // PackageElementimport 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 }}
其中 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; } } } }
更好的方式是以 map
键值对来存储资源类 。后期我会做详细讲解。
3、生成 xxxx$$ViewBinder.java (xxxx 代码 Activity、View、Dialog)文件
4、给生成 xxxx$$ViewBinder.java 添加资源信息
5、通过 ButterKnife.bind(this);
反射得到 xxxx$$ViewBinder.java 类,并调用注入资源的方法
由于篇幅原因,本文就到这。如果你有什么疑问?欢迎加群解答
源码地址
- 自定义注解框架的那些事
- 注解的那些事
- Android 自定义注解框架
- 自定义注解框架实现
- Android ViewUtils注解框架自定义
- 自定义基于注解形式的Excel解析器框架
- 注解的理解、自定义注解
- 实现基于注解(Annotation)的数据库框架(三)自定义注解(Annotation)
- 关于自定义Dialog的那些事
- SpringMVC自定义处理器里的那些事
- 架构的那些事1--分层框架
- iOS开发关于"框架"的那些事
- 关于注解反射那些事
- Hibernate的那些事-manyToOne注解映射(List集合)
- Hibernate的那些事-manyToOne注解映射(Map集合)
- Hibernate的那些事-manyToOne注解映射(Map集合)
- Hibernate的那些事-manyToOne注解映射(List集合)
- Hibernate的那些事-manyToOne注解映射(Map集合)
- 超级牛逼的线段树总结 啊哈哈~~~
- IntelliJ IDEA 2017 免费激活方法
- 小程序获取用户信息方式更新了!!
- HDU 6103 Kirinriki
- sphinx 的安装与应用
- 自定义注解框架的那些事
- WordPress需要删除的13个函数
- 客户端调用CXF2.2.3 webservice
- [tensorflow 入门]ReLU 与MNIST卷积
- 应用数据源Druid监控SQL语句的执行情况配置
- linux 内核自旋锁spinlock实现详解(基于ARM处理器)
- hadoop IO操作
- tcp滑动窗口以拥塞窗口和各种缓冲的总结
- POJ3061-Subsequence