注解java-library

build.gradle

apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])
}// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {options.encoding = "UTF-8"
}sourceCompatibility = "7"
targetCompatibility = "7"

BindView 注解

// SOURCE 注解仅在源码中保留,class文件中不存在
// CLASS 注解在源码和class文件中都存在,但运行时不存在
// RUNTIME 注解在源码,class文件中存在且运行时可以通过反射机制获取到
@Target(ElementType.FIELD) // 注解作用在属性之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface  BindView {// 返回R.id.xx值int value();
}

OnClick 注解

@Target(ElementType.METHOD) // 注解作用在方法之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface OnClick {// 此处省略了int[]int value();
}

注解使用的实体类或者接口

ViewBinder 接口

/*** 接口绑定类(所有注解处理器生的类,都需要实现该接口,= 接口实现类)*/
public interface ViewBinder<T> {void bind(T target);
}

DebouncingOnClickListener 抽象类


// 点击监听接口,实现类(抽象类 + 抽象方法)
public abstract class DebouncingOnClickListener implements View.OnClickListener {@Overridepublic void onClick(View v) {// 调用抽象方法doClick(v);}public abstract void doClick(View v);
}

ButterKnife 核心类

/*** 核心类,接口 = 接口实现类* ButterKnife用的是构造方法.newInstance()* 接口.bind()*/
public class ButterKnife {public static void bind(Activity activity) {// 拼接类名,如:MainActivity$ViewBinderString className = activity.getClass().getName() + "$ViewBinder";try {// 加载上述拼接类Class<?> viewBinderClass = Class.forName(className);// 接口 = 接口实现类ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();// 调用接口方法viewBinder.bind(activity);} catch (Exception e) {e.printStackTrace();}}
}

注解处理器java-library

build.gradle

apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])// As3.5 + gradle5.4.1-allcompileOnly 'com.google.auto.service:auto-service:1.0-rc6'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'// 帮助我们通过类调用的形式来生成Java代码implementation "com.squareup:javapoet:1.10.0"// 引入annotation,处理@BindView、@Onclick注解implementation project(':annotation')
}// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {options.encoding = "UTF-8"
}sourceCompatibility = "7"
targetCompatibility = "7"

ButterKnifeProcess 注解处理核心类

// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.BINDVIEW_ANNOTATION_TYPES, Constants.ONCLICK_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcess extends AbstractProcessor {// 操作Element工具类 (类、函数、属性都是Element)private Elements elementUtils;// type(类信息)工具类,包含用于操作TypeMirror的工具方法private Types typeUtils;// Messager用来报告错误,警告和其他提示信息private Messager messager;// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件private Filer filer;// key:类节点, value:被@BindView注解的属性集合private Map<TypeElement, List<VariableElement>> tempBindViewMap = new HashMap<>();// key:类节点, value:被@OnClick注解的方法集合private Map<TypeElement, List<ExecutableElement>> tempOnClickMap = new HashMap<>();// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);// 初始化elementUtils = processingEnvironment.getElementUtils();typeUtils = processingEnvironment.getTypeUtils();messager = processingEnvironment.getMessager();filer = processingEnvironment.getFiler();messager.printMessage(Diagnostic.Kind.NOTE,"注解处理器初始化完成,开始处理注解------------------------------->");}/*** 相当于main函数,开始处理注解* 注解处理器的核心方法,处理具体的注解,生成Java文件** @param set              使用了支持处理注解的节点集合* @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。* @return true 表示后续处理器不会再处理(已经处理完成)*/@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {// 一旦有属性上使用@BindView注解if (!EmptyUtils.isEmpty(set)) {// 获取所有被 @BindView 注解的 元素集合Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);// 获取所有被 @OnClick 注解的 元素集合Set<? extends Element> onClickElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);if (!EmptyUtils.isEmpty(bindViewElements) || !EmptyUtils.isEmpty(onClickElements)) {try {// 赋值临时map存储,用来存放被注解的属性集合valueOfMap(bindViewElements, onClickElements);// 生成类文件,如:createJavaFile();return true;} catch (IOException e) {e.printStackTrace();}}}return false;}private void createJavaFile() throws IOException {// 判断是否有需要生成的类文件if (!EmptyUtils.isEmpty(tempBindViewMap)) {// 获取ViewBinder接口类型(生成类文件需要实现的接口)TypeElement viewBinderType = elementUtils.getTypeElement(Constants.VIEWBINDER);TypeElement clickListenerType = elementUtils.getTypeElement(Constants.CLICKLISTENER);TypeElement viewType = elementUtils.getTypeElement(Constants.VIEW);for (Map.Entry<TypeElement, List<VariableElement>> entry : tempBindViewMap.entrySet()) {// 类名ClassName className = ClassName.get(entry.getKey());// 实现接口泛型ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBinderType),ClassName.get(entry.getKey()));// 参数体配置(MainActivity target)ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), // MainActivityConstants.TARGET_PARAMETER_NAME) // target.addModifiers(Modifier.FINAL).build();// 方法配置:public void bind(MainActivity target) {MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME) // 方法名.addAnnotation(Override.class) // 重写注解.addModifiers(Modifier.PUBLIC) // public修饰符.addParameter(parameterSpec); // 方法参数for (Element fieldElement : entry.getValue()) {// 获取属性名String fieldName = fieldElement.getSimpleName().toString();// 获取@BindView注解的值int annotationValue = fieldElement.getAnnotation(BindView.class).value();// target.tv = target.findViewById(R.id.tv);String methodContent = "$N." + fieldName + " = $N.findViewById($L)";methodBuidler.addStatement(methodContent,Constants.TARGET_PARAMETER_NAME,Constants.TARGET_PARAMETER_NAME,annotationValue);}if (!EmptyUtils.isEmpty(tempOnClickMap)) {for (Map.Entry<TypeElement, List<ExecutableElement>> methodEntry : tempOnClickMap.entrySet()) {// 类名if (className.equals(ClassName.get(entry.getKey()))) {for (ExecutableElement methodElement : methodEntry.getValue()) {// 获取方法名String methodName = methodElement.getSimpleName().toString();// 获取@OnClick注解的值int annotationValue = methodElement.getAnnotation(OnClick.class).value();/*** target.findViewById(2131165312).setOnClickListener(new DebouncingOnClickListener() {*      public void doClick(View view) {*          target.click(view);*      }* });*/methodBuidler.beginControlFlow("$N.findViewById($L).setOnClickListener(new $T()",Constants.TARGET_PARAMETER_NAME, annotationValue, ClassName.get(clickListenerType)).beginControlFlow("public void doClick($T view)", ClassName.get(viewType)).addStatement("$N." + methodName + "(view)", Constants.TARGET_PARAMETER_NAME).endControlFlow().endControlFlow(")").build();}}}}// 必须是同包(属性修饰符缺省),MainActivity$$ViewBinderJavaFile.builder(className.packageName(), // 包名TypeSpec.classBuilder(className.simpleName() + "$ViewBinder") // 类名.addSuperinterface(typeName) // 实现ViewBinder接口.addModifiers(Modifier.PUBLIC) // public修饰符.addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体).build()) // 类构建完成.build() // JavaFile构建完成.writeTo(filer); // 文件生成器开始生成类文件}}}private void valueOfMap(Set<? extends Element> bindViewElements, Set<? extends Element> onClickElements) {if (!EmptyUtils.isEmpty(bindViewElements)) {for (Element element : bindViewElements) {messager.printMessage(Diagnostic.Kind.NOTE, "@BindView >>> " + element.getSimpleName());if (element.getKind() == ElementKind.FIELD) {VariableElement fieldElement = (VariableElement) element;// 注解在属性之上,属性节点父节点是类节点TypeElement enclosingElement = (TypeElement) fieldElement.getEnclosingElement();// 如果map集合中的key:类节点存在,直接添加属性if (tempBindViewMap.containsKey(enclosingElement)) {tempBindViewMap.get(enclosingElement).add(fieldElement);} else {List<VariableElement> fields = new ArrayList<>();fields.add(fieldElement);tempBindViewMap.put(enclosingElement, fields);}}}}if (!EmptyUtils.isEmpty(onClickElements)) {for (Element element : onClickElements) {messager.printMessage(Diagnostic.Kind.NOTE, "@OnClick >>> " + element.getSimpleName());if (element.getKind() == ElementKind.METHOD) {ExecutableElement methodElement = (ExecutableElement) element;// 注解在属性之上,属性节点父节点是类节点TypeElement enclosingElement = (TypeElement) methodElement.getEnclosingElement();// 如果map集合中的key:类节点存在,直接添加属性if (tempOnClickMap.containsKey(enclosingElement)) {tempOnClickMap.get(enclosingElement).add(methodElement);} else {List<ExecutableElement> fields = new ArrayList<>();fields.add(methodElement);tempOnClickMap.put(enclosingElement, fields);}}}}}
}

Constants 常量类

/*** 常量类*/
public class Constants {// 注解处理器中支持的注解类型public static final String BINDVIEW_ANNOTATION_TYPES = "com.sanguine.butterknife.annotation.BindView";public static final String ONCLICK_ANNOTATION_TYPES = "com.sanguine.butterknife.annotation.OnClick";// 布局、控件绑定实现接口public static final String VIEWBINDER = "com.sanguine.butterknife.library.ViewBinder";public static final String CLICKLISTENER = "com.sanguine.butterknife.library.DebouncingOnClickListener";public static final String VIEW = "android.view.View";// bind方法名public static final String BIND_METHOD_NAME = "bind";// bind方法的参数名targetpublic static final String TARGET_PARAMETER_NAME = "target";
}

EmptyUtils 判空类


/*** 字符串、集合判空工具*/
public final class EmptyUtils {public static boolean isEmpty(CharSequence cs) {return cs == null || cs.length() == 0;}public static boolean isEmpty(Collection<?> coll) {return coll == null || coll.isEmpty();}public static boolean isEmpty(final Map<?, ?> map) {return map == null || map.isEmpty();}}

总结

本文通过APT+JavaPoet技术实现编译器解析注解,并且生成Java文件。APT负责解析注解,JavaPoet负责生成Java文件。最终实现了ButterKnife。在之前组件化的时候,我们同样有用过这样的技术,当时是通过生成的问题实现组件化之间的通信。

ButterKnife系列之手写实现相关推荐

  1. MATLABSTM32CubeMX联合开发系列——不用手写一行代码就能实现CAN通讯

    MATLAB&STM32CubeMX联合开发系列--不用手写一行代码就能实现CAN通讯 从第一次搭建好MATLAB和STM32CubeMX的联合开发环境有一段时间了,之前已经发布了两个实例分享 ...

  2. [软件评测第十期]神器系列之手写计…

    [软件评测第十期]神器系列之手写计算器MyScript Calculator 作者:张羊羊 软件名称: MyScript Calculator 评测软件类型:工具软件.学习软件.生活实用类软件 软件简 ...

  3. 【Linux服务器开发系列】手写一个用户态网络协议栈,瞬间提升你网络功底丨netmap/dpdk的实现

    手写一个用户态网络协议栈,瞬间提升你网络功底 1. 网卡基础架构 2. netmap/dpdk的实现 3. 网络协议栈实战 [Linux服务器开发系列]手写一个用户态网络协议栈,瞬间提升你网络功底丨n ...

  4. 【Linux服务器开发系列】手写用户态协议栈,udpipeth数据包的封装,零拷贝的实现,柔性数组

    视频教你手写网络协议栈,保证大家能学会,耐心看 1. 用户态协议栈 2. udp/ip/eth数据包的封装 3. 零拷贝的实现 4. 零长数组(柔性数组) [Linux服务器开发系列]手写用户态协议栈 ...

  5. 安全系列之——手写JAVA加密、解密

    其他文章: 安全系列之--手写JAVA加密.解密 安全系列之--数据传输的完整性.私密性.源认证.不可否认性 安全系列之--主流Hash散列算法介绍和使用 安全系列之--RSA的公钥私钥有多少人能分的 ...

  6. 深度学习数字仪表盘识别_【深度学习系列】手写数字识别实战

    上周在搜索关于深度学习分布式运行方式的资料时,无意间搜到了paddlepaddle,发现这个框架的分布式训练方案做的还挺不错的,想跟大家分享一下.不过呢,这块内容太复杂了,所以就简单的介绍一下padd ...

  7. 【FFT夯实基础系列】手写笔记合集|傅里叶级数、傅里叶变换

    B站up主@mazonex推出的宝藏系列视频课笔记 因为是数学类的,所以笔者采用了手写并导出的形式 这一篇合集包括了up主[傅里叶夯实基础系列]视频的P1-P6,可以通过上方链接看具体视频 目录 傅里 ...

  8. 手写系列之手写LM(Levenberg–Marquardt)算法(基于eigen)

    紧接上次的手写高斯牛顿,今天顺便将LM算法也进行了手写实现,并且自己基于eigen的高斯牛顿进行了对比,可以很明显的看到,LM的算法收敛更快,精度也略胜一筹,这次高博的书不够用了,参考网上伪代码进行实 ...

  9. Top K问题系列之三 手写代码

    Top K问题是面试时手写代码的常考题,某些场景下的解法与堆排和快排的关系紧密,所以把它放在堆排后面讲. 关于Top K问题最全的分类总结是在这里(包括海量数据的处理),个人将这些题分成了两类:一类是 ...

最新文章

  1. 我的人工智能机器人的游戏
  2. json数据格式 与 for in
  3. ubuntu14.04安装intel openCL
  4. 再次发布SQL Prompt 3.8的新的patch,解决了不能格式化T-SQL的问题
  5. Kotlin-Learning 扩展
  6. 新建JavaWeb项目
  7. abap 字符串处理
  8. java将030A转换为方块_JAVA试题
  9. fast.ai 深度学习笔记:第一部分第五课
  10. 人工智能芯片的前世与今生
  11. Google地图接口API之Google地图 API 参考手册(七)
  12. 怎样将网络机顶盒usb调试模式打开
  13. jsp java session_JSP Session
  14. 要求用户首先输入员工数量,然后输入相应员工信息,格式为: name,age,gender,salary,hiredate 例如: 张三,25,男,5000,2006-02-15 每一行为一个员
  15. 服务开通语音通知功能如何实现?服务开通语音提醒功能实现方案
  16. php生成临时文件,php生成zip压缩文件的方法详解
  17. python中if none什么意思_关于python:“ if x”和“ if x not None”之间的区别
  18. 解析LDO的五大作用,这里有你意想不到的答案
  19. 试炼四:switch选择结构
  20. 新一代人工智能知识体系大全

热门文章

  1. Excel表格中如何快速插入多个空白行
  2. 深入剖析Spring架构与设计原理(一)
  3. 平行四边形的效果实现
  4. 如何将Python打包后的exe还原成.py?
  5. Tensorflow基础知识
  6. Dva 的connect使用
  7. 积分商城系统业务逻辑思维导图_怎么开发积分商城系统_OctShop
  8. 用博奥如何导入单项工程电子表_博奥工程系列软件实操手册第六册.doc
  9. qt显示温度℃度数°或中文等特殊符号
  10. Sedona NetFusion 在OIF/ONF T-API 互通测试中扮演关键角色