ButterKnife实现之Android注解处理器使用教程

ButterKnife实现之Android注解处理器使用教程

1、新建一个注解

1.1、编译时注解

创建注解所需的元注解@Retention包含3个不同的值,RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME。这3个值代表注解不同的保留策略。
使用RetentionPolicy.RUNTIME的注解为运行时注解,能在程序运行时通过反射获取注解的信息并进行逻辑处理;使用RetentionPolicy.CLASS的注解为编译时注解,能在程序编译时进行预处理操作,比如生成一些辅助代码;使用RetentionPolicy.SOURCE的注解能做一些检查性的操作,比如@Override和@SuppressWarning。

1.2、新建注解

编译时注解能够帮助我们生成辅助代码,能够满足在编译时获取注解信息生成带有findViewById的代码。所以我们新建一个编译时注解。新建注解前,我们新建一个名为annotation的Java Library类型的Module。然后在这个Module新建这个注解,命名为BindView,代码如下:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {int value();
}

2、新建注解处理器

注解处理器是处理注解的类,处理编译时注解时我们需要编写一个注解处理器。注解处理器类需要继承AbstractProcessor类。本节我们来学习编写注解处理器,跟上一节一样我们再次新建一个Java Libary的module,这个module命名为processor,并依赖包含注解的annotation Module:在processor module的build.gradle添加如下代码:

dependencies{implementation project(':annotation')
}

接着,在这个module中我们新建一个注解处理器-MainProcessor,它继承AbstractProcessor并实现AbstractProcess的4大方法,我们来学习这4大方法。

2.1、AbstractProcessor的4大方法

/**
* 注解处理器MainProcessor
*/
public class MainProcessor extends AbstractProcessor {@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {return false;}@Overridepublic Set<String> getSupportedAnnotationTypes() {return super.getSupportedAnnotationTypes();}@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}

继承AbstractProcessor需要重写上述代码段的4个方法,依次介绍它们的作用:
1、init方法:被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供了很多有用的工具类,比如Elements、Types、Filer和Messager等。
2、process方法:相当于每个处理器的主函数main(),在这里编写扫描、评估和处理注解的代码以及生成Java文件。输入参数RoundEnviroment,可以让你查询包含特定注解的被注解元素。
3、getSupportedAnnotationTypes:这是必须指定的方法,指定这个注解处理器是注册给哪个注解的,注意,它的返回值是一个字符串的集合,包含该处理器想要处理的注解类型的合法全称。
4、getSupportedSourceVersion:用来指定你使用的Java版本,通常这里返回SourceVersion.latestSupported()。
可以将MainProcessor的getSupportAnnotationTypes方法和getSupportedSourceVersion方法更新成如下:

    @Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> typeSet = new HashSet<>();typeSet.add(BindView.class.getCanonicalName());return typeSet;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}

2.2、JavaPoet的使用

前面说到过,在程序编译时根据注解信息生成辅助文件,JavaPoet是一个可以生成Java代码的第三方框架,所以我们要利用它生成辅助文件。

1.添加JavaPoet依赖
    implementation 'com.squareup:javapoet:1.7.0'
2.JavaPoet Api使用

1、生成方法
以ButterKnife的bind方法为例,初始化一个id为R.id.tv_hello的TextView,代码如下:

//这个MainActivity是个例子,实际上使用的是注解所对应的Activity
public void bind(MainActivity activity){activity.tvHello = (TextView)(((android.app.Activity)activity).findViewById(R.id.tv_hello));
}

使用JavaPoet生成这个方法:

MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("bind") //方法名为bind.addModifiers(Modifier.PUBLIC) //方法修饰符:Public.addParameter(MainActivity,"activity")//方法的参数:如MainActivity activity.returns(void.class); //返回值:voidString code = String.format("activity.%s=(%s)(((android.app.Activity)activity).findViewById(%s));\n","tvHello","android.widget.TextView",R.id.tv_hello);
bindMethodBuilder.addCode(code);

2、生成类
以生成MainActivity的辅助类MainActivity_ViewBinding为例,类的内容:

public class MainActivity_ViewBinding{//bind方法就是上面生成的方法public void bind(MainActivity activity){tvTest = (android.widget.TextView) ((android.app.Activity)activity).findViewById(R.id.tv_test);}
}

使用JavaPoet生成该类:

TypeSpec.classBuilder("MainActivity_ViewBinding").addModifiers(Modifier.PUBLIC).addMethod(bindMethodBuilder.build()).build();

这样的话,完整的类就使用JavaPoet生成出来了。还有更多的JavaPoet的用法,推荐看这篇文章:基于JavaPoet自动生成java代码文件

2.3、编写process方法

接下来就是注解处理器的核心部分了,我们通过process方法实现注解解析,生成源码的功能。process方法中需要用到ProcessingEnviroment参数,所以我们先处理init方法,保存变量:

public class MainProcessor extends AbstractProcessor {private Elements elementUtils;private ProcessingEnvironment processingEnvironment;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);elementUtils = processingEnv.getElementUtils();processingEnvironment = processingEnv;}
}

process方法的逻辑主要是解析注解和生成代码,我就直接上代码了:

 @Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {/**生成的代码* 类:MainActivity_ViewBinding,包名:com.wei.annotation_processor_demo* 内容:* public class MainActivity_ViewBinding{*      public void bind(MainActivity activity){*          tvTest = (TextView) ((Activity)activity).findViewById(R.id.tv_test);*      }* }*/Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);Map<VariableElement, Integer> elementMap = new HashMap<>();for (Element element : elementsAnnotatedWith) {//获取被注解的字段VariableElement variableElement = (VariableElement) element;//获取被注解的字段的类TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();String className = enclosingElement.getSimpleName().toString();//获取注解BindView bindView = variableElement.getAnnotation(BindView.class);int id = bindView.value();//保存所有被注解的字段和注解的成员变量值,用于生成代码elementMap.put(variableElement, id);//获取被注解的字段所在类的包名String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();//生成代码TypeSpec typeSpec = generateCode(className, ClassName.bestGuess(enclosingElement.getQualifiedName().toString()), elementMap);//生成javaFileJavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();try {//生成java代码javaFile.writeTo(processingEnvironment.getFiler());} catch (IOException e) {e.printStackTrace();}}return true;}private TypeSpec generateCode(String className, ClassName parameterClass, Map<VariableElement,Integer> elementMap){//生成bind方法MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC).addParameter(parameterClass,"activity").returns(void.class);for (Map.Entry<VariableElement, Integer> entry : elementMap.entrySet()) {String fieldName = entry.getKey().getSimpleName().toString();String fieldType = entry.getKey().asType().toString();String code = String.format("activity.%s=(%s)(((android.app.Activity)activity).findViewById(%s));\n",fieldName,fieldType,String.valueOf(entry.getValue()));processingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"fieldName:"+fieldName+",fieldType:"+fieldType+",code:"+code);bindMethodBuilder.addCode(code);}return TypeSpec.classBuilder(className+"_ViewBinding").addModifiers(Modifier.PUBLIC).addMethod(bindMethodBuilder.build()).build();}

2.4、注册注解处理器

为了能使用注解处理器,需要用一个服务文件来注册它。文件路径为:processor module的根目录/resources/META-INF.services/javax.annotation.processing.Processor。在javax.annotation.processing.Processor中添加内容:com.wei.processor.MainProcessor。这样就成功注册了注解处理器,同时需要注意2点:1.文件路径中的文件夹可能不存在,需要手动创建;2.文件内容是注解处理器的包名+类名,不要照抄我的。

AutoService

如果不想手动添加服务文件,就使用AutoService框架来生成服务文件。
使用步骤:
1、添加依赖

//google autoService
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor "com.google.auto.service:auto-service:1.0-rc4"

2、使用
在注解处理器的类上添加@AutoService注解即可:

@AutoService(Processor.class)
public class MainProcessor extends AbstractProcessor {
//省略内容
//...
}

这样就实现了刚才我们手动创建服务文件同样的功能。

3、使用

注解处理器编写结束了,我们需要验证是否能够实现ButterKnife同样的效果。验证方法:我们在app module中添加annotation、processor两个库的依赖,在MainActivity中使用BindView注解,看看app module根目录/build/ap_generated_sources/debug/out/有无MainActivity_ViewBinding文件生成。

添加依赖:
 implementation project(":annotation")
//    implementation project(":processor")annotationProcessor project(":processor")

使用annotationProcessor代替implementation有以下好处:
1、annotationProcessor引用的库只会在编译期间被依赖使用,不会打包进入apk,因为注册处理器是在编译期间使用的,打包进入apk会占用空间
2、为注解处理器生成的代码设置好路径,以便Android Studio能找到它

使用BindView注解
public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_hello)TextView tvHello;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}
查看生成文件

生成文件

使用

生成ViewBinding类后,可以通过反射执行该类bind方法,实现findViewById逻辑:

private void bind() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {Class<?> clazz = Class.forName(getClass().getName() + "_ViewBinding");System.out.println(getClass().getName());Method bind = clazz.getDeclaredMethod("bind", getClass());bind.invoke(clazz.newInstance(), this);
}

调用这个方法也就实现了findViewById逻辑,最后:

@BindView(R.id.tv_hello)
TextView tvHello;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);try {bind();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}tvHello.setText("我成功了!");
}

4、参考文章

感谢一下文章提供的教程,万分感激:
1、Android APT技术学习
2、基于JavaPoet自动生成java代码文件
3、深入理解编译注解(二)annotationProcessor与android-apt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/272449.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flink重温笔记(十三): flink 高级特性和新特性(2)——ProcessFunction API 和 双流 join

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 13 天啦&#xff01;学习了 flink 高级特性和新特性之ProcessFunction API 和 双流 join&#xff0c;主要是解决大数据领域数据从数据增量聚合的问题&#xff0c;以及快速变化中的流数据拉宽问题&#xff0c;即变化中多个…

idea连接远程服务器

1. 双击shift&#xff0c;出现如下界面 2. 远程连接 原文来自这个up主的&#xff0c;点击蓝色字体就可以跳转啦&#xff01; 输入主机ip、用户名、密码&#xff0c;点击Test Connection验证&#xff0c;最后点击ok添加成功 有用的话记得给俺点个赞&#xff0c;靴靴~

出现身份验证错误,无法连接到本地安全机构 顺利解决这个问题希望能帮助大家

出现身份验证错误&#xff0c;无法连接到本地安全机构&#xff0c;远程计算机&#xff1a;XX&#xff0c;这可能是由于密码过期&#xff0c;如果密码已过期请更新密码。 我们可以在系统属性中对远程进行设置&#xff0c;以解决远程桌面无法连接到本地安全机构这一问题。 步骤…

AndroidStudio跑马灯实现

在activity_main.xml中编写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_h…

如何配置IDEA中的JavaWeb环境(2023最新版)

创建项目 中文版&#xff1a;【文件】-【新建】-【项目】 点击【新建项目】&#xff0c;改好【名称】点击【创建】 右键自己建立的项目-【添加框架支持】&#xff08;英文版是Add Framework Support...&#xff09; 勾选【Web应用程序】-【确定】 配置tomcat 点击编辑配置 点…

保姆级讲解字符串函数(上篇)

目录 字符分类函数 导图 函数介绍 1.getchar 2. isupper 和 islower 字符转换函数&#xff1a;&#xff08;toupper , tolower&#xff09; 与 putchar 字符串函数 导图 string函数的使用和模拟实现 string的使用 求字符串长度 字符串的比较 string函数的模拟实现…

基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 假设一个收集轨道&#xff0c;上面有5个采集堆&#xff0c;这5个采集堆分别被看作一个4*20的矩阵&#xff08;下面只有4*10&#xff09;&#xff0c;每个模块&…

Java多线程学习的关键要点和常见案例总结

文章目录 Java多线程学习的关键要点&#xff1a;案例示例&#xff1a; Java多线程编程还包括更多的高级特性和实用技巧高级主题&#xff1a;实用案例&#xff1a;线程池的高级用法和配置&#xff1a;线程安全的最佳实践&#xff1a; Java多线程学习的关键要点和常见案例总结如下…

防患未然,OceanBase巡检工具应用实践——《OceanBase诊断系列》之五

1. OceanBase为什么要做巡检功能 尽管OceanBase拥有很好的MySQL兼容性&#xff0c;但在长期的生产环境中&#xff0c;部署不符合标准规范、硬件支持异常&#xff0c;或配置项错误等问题&#xff0c;这些短期不会出现的问题&#xff0c;仍会对数据库集群构成潜在的巨大风险。为…

血泪教训双非计算机考研避坑指南

记住&#xff0c;考研不是要考多少分&#xff0c;而是要上岸&#xff0c;上岸&#xff0c;上岸&#xff01;&#xff01;&#x1f621; 一、坏、渣、难、险&#xff0c;一律打咩 坏: 歧视本科院校‼️ 这个就不用多说了&#xff0c;你明明付出了大于等于别人的努力&#xff0c;…

猫头虎分享已解决Bug || 数据中心断电:PowerLoss, DataCenterBlackout

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

运维打工人,兼职跑外卖的第二个周末

北京&#xff0c;晴&#xff0c;西南风1级。 前序 今天天气还行&#xff0c;赶紧起来&#xff0c;把衣服都洗洗&#xff0c;准备准备&#xff0c;去田老师吃饭早饭了。 一个甜饼、一个茶叶蛋、3元自助粥花费7.5。5个5挺吉利的。 跑外卖的意义 两个字减肥&#xff0c;记录刚入…

JavaWeb03-HTTP协议,Tomcat,Servlet

目录 一、HTTP协议 1.概述 2.特点 3.请求数据格式 &#xff08;1&#xff09;请求行 &#xff08;2&#xff09;请求头 &#xff08;3&#xff09;请求体 &#xff08;4&#xff09;常见请求头 &#xff08;5&#xff09;GET和POST请求区别 4.响应数据格式 &#xf…

分销商城微信小程序:用户粘性增强,促进复购率提升

在数字化浪潮的推动下&#xff0c;微信小程序作为一种轻便、高效的移动应用形式&#xff0c;正成为越来越多企业开展电商业务的重要平台。而分销商城微信小程序的出现&#xff0c;更是为企业带来了前所未有的机遇。通过分销商城微信小程序&#xff0c;企业不仅能够拓宽销售渠道…

PyQt5实现远程更新exe可执行文件

PyQt5实现远程下载更新exe可执行文件 1、实现流程 1、获取远程http地址 2、获取需要更新的exe文件 3、点击更新 4、把exe强关闭 5、下载文件 6、更新2、效果图 3、示例代码 conf.ini配置文件: {"http_address_edit_value": "http://xxx.com/xxx/xxx.exe&qu…

数据结构从入门到精通——队列

队列 前言一、队列1.1队列的概念及结构1.2队列的实现1.3队列的实现1.4扩展 二、队列面试题三、队列的具体实现代码Queue.hQueue.ctest.c队列的初始化队列的销毁入队列出队列返回队头元素返回队尾元素检测队列是否为空检测元素个数 前言 队列是一种特殊的线性数据结构&#xff…

【蓝桥杯-单片机】基础模块LED和按键

文章目录 【蓝桥杯-单片机】Led、按键等基础模块01 前置准备&#xff08;1&#xff09;新建工程&#xff08;4&#xff09;编写程序 02 基础模块&#xff1a;LED&#xff08;0&#xff09;LED原理图&#xff08;1&#xff09;对P1整体赋值&#xff0c;控制所有的LED灯&#xff…

three.js如何实现简易3D机房?(一)基础准备-下

接上一篇&#xff1a; three.js如何实现简易3D机房&#xff1f;&#xff08;一&#xff09;基础准备-上&#xff1a;http://t.csdnimg.cn/MCrFZ 目录 四、按需引入 五、导入模型 四、按需引入 index.vue文件中 <template><div class"three-area">&l…

算法第二十五天-寻找排序数组中的最小值

寻找排序数组中的最小值 题目要求 解题思路 二分法 代码 class Solution:def findMin(self, nums: List[int]) -> int:low, high 0, len(nums) - 1while low < high:pivot low (high - low) // 2if nums[pivot] < nums[high]:high pivot else:low pivot 1re…

计算两帧雷达数据之间的变换矩阵

文章目录 package.xmlCMakeLists.txtpoint_cloud_registration.cc运行结果 package.xml <?xml version"1.0"?> <package format"2"><name>point_cloud_registration</name><version>0.0.0</version><descriptio…