当前位置: 首页 > news >正文

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

相关文章:

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

ButterKnife实现之Android注解处理器使用教程 1、新建一个注解 1.1、编译时注解 创建注解所需的元注解Retention包含3个不同的值&#xff0c;RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME。这3个值代表注解不同的保留策略。 使用RetentionPolic…...

【哈希】Leetcode 128. 最长连续序列 【中等】

最长连续序列 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。示例 1&#xff1a;输入&#xff1a;nums [100,4,200,1,3,2]输出&#x…...

回溯是怎么回事(算法村第十八关青铜挑战)

组合 77. 组合 - 力扣&#xff08;LeetCode&#xff09; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],…...

向爬虫而生---Redis 探究篇5<Redis集群刨根问底(1)>

前言: Redis集群是一种可靠和高性能的分布式数据库解决方案。随着互联网的迅速发展和数据规模的增长,传统的单机Redis已经无法满足大规模应用的需求。Redis集群的出现填补了这一空白,提供了更高的可扩展性和容错性。 大家都知道,Redis是一种基于内存的高性能键值存储数据库,…...

系统集成Prometheus+Grafana

根据产品需求在自己的系统中添加一个系统监控的页面&#xff0c;其中有主机信息的显示&#xff0c;也有一些业务信息的显示。调研后的方案是 主机信息通过Prometheus采集和存储&#xff0c;业务信息通过自己系统的调度任务统计后存储在Mysql中&#xff0c;使用Grafana对接Prome…...

实例驱动计算机网络

文章目录 计算机网络的层次结构应用层DNSHTTP协议HTTP请求响应过程 运输层TCP协议TCP协议面向连接实现TCP的三次握手连接TCP的四次挥手断开连接 TCP协议可靠性实现TCP的流量控制TCP的拥塞控制TCP的重传机制 UDP协议 网际层IP协议&#xff08;主机与主机&#xff09;IP地址的分类…...

Unity 报错:SSL CA certificate error

使用UnityWebRequest时出现如下报错&#xff1a; SSL CA certificate error Curl error 60: Cert verify failed: UNITYTLS_X509VERIFY_FLAG_USER_ERROR1 原因&#xff1a; 证书验证失败 和 SSL CA证书错误 解决方法&#xff1a; 创建一个如下的类&#xff1a; /// <…...

算法刷题Day1 | 704.二分查找、27.移除元素

目录 0 引言1 二分查找1.1 我的解题1.2 修改后1.3 总结 2 移除元素2.1 暴力求解2.2 双指针法&#xff08;快慢指针&#xff09; &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;代码随想录算法训练营第一天…...

大数据技术学习笔记(五)—— MapReduce(2)

目录 1 MapReduce 的数据流1.1 数据流走向1.2 InputFormat 数据输入1.2.1 FileInputFormat 切片源码、机制1.2.2 TextInputFormat 读数据源码、机制1.2.3 CombineTextInputFormat 切片机制 1.3 OutputFormat 数据输出1.3.1 OutputFormat 实现类1.3.2 自定义 OutputFormat 2 Map…...

北斗导航 | 同步双星故障的BDS/GPS接收机自主完好性监测算法

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 同步双星故障的BDS/GPS接收机自主完好性监测算法 1 引言2 同步双星故障…...

2024金三银四必看前端面试题!简答版精品!

文章目录 导文面试题 导文 2024金三银四必看前端面试题&#xff01;2w字精品&#xff01;简答版 金三银四黄金期来了 想要跳槽的小伙伴快来看啊 面试题 基于您给出的方向&#xff0c;我将为您生成20个面试题和答案。请注意&#xff0c;由于面试题的答案可能因个人经验和理解而…...

Python-sklearn.datasets-make_blobs

​​​​​​sklearn.datasets.make_blobs()函数形参详解 """ Title: datasets for regression Time: 2024/3/5 Author: Michael Jie """from sklearn import datasets import matplotlib.pyplot as plt# 产生服从正态分布的聚类数据 x, y, cen…...

[最佳实践] conda环境内安装cuda 和 Mamba的安装

Mamba安装失败的过程中&#xff0c;causal-conv1d安装报错为连接超时 key word: vision mamba&#xff0c; DL &#xff0c;深度学习 &#xff0c;mamba unet&#xff0c;mamba环境安装 Mamba安装 主要故障是 pip install causal-conv1d1.2.0和 pip install mamba-ssm1.2.0 安…...

【算法】顺时针打印矩阵(图文详解,代码详细注释

目录 题目 代码如下: 题目 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。例如:如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则打印出数字:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10 这一道题乍一看,没有包含任何复杂的数据结构和…...

蚂蚁感冒c++

题目 思路 “两蚂蚁碰面会掉头&#xff0c;若其中一只蚂蚁感冒了&#xff0c;会把感冒传染给碰到的蚂蚁”&#xff0c;这句话看作是“两蚂蚁碰面会互相穿过&#xff0c;只是把感冒的状态传给了另一只蚂蚁”&#xff0c;因为哪只蚂蚁感冒了并不是题目的重点&#xff0c;重点是有…...

python Plotly可视化

文章目录 Plotly常用函数PolarPlotlymake_subplotsadd_tracego.Scatterpolarglupdate_tracesupdate_layout综合示例 完整版 Python在数据可视化方面有着丰富的库和函数&#xff0c;其中一些常用的库包括 Matplotlib、Seaborn、Plotly、Bokeh等。 Plotly是一个交互式绘图库&…...

刷题第10天

代码随想录刷题第10天 |● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 239. 滑动窗口最大值 唉&#xff0c;好难&#xff0c;先记个思路吧 class Solution { private:class MyQueue { //单调队列&#xff08;从大到小&#xff09;public:deque<int> que; // 使用deq…...

Bililive-go 实现直播自动监控录制

前言 最近有直播录制的需求&#xff0c;但是自己手动录制太麻烦繁琐&#xff0c;于是用了开源项目Bililive-go进行全自动监控录制&#xff0c;目前这个项目已经有3K stars了 部署 为了方便我使用了docker compose 部署 version: 3.8 services:bililive:image: chigusa/bilil…...

【Redis】Redis持久化模式RDB

目录 引子 RDB RDB的优缺点 小节一下 引子 不论把Redis作为数据库还是缓存来使用&#xff0c;他肯定有数据需要持久化&#xff0c;这里我们就来聊聊两种持久化机制。这两种机制&#xff0c;其实是 快照 与 日志 的形式。快照:就是当前数据的备份&#xff0c;我可以拷贝到磁…...

Java基础 - 模拟医院挂号系统

模拟医院挂号系统功能 1. 科室管理&#xff1a;新增科室&#xff0c;删除科室&#xff08;如果有医生在&#xff0c;则不能删除该科室&#xff09;&#xff0c;修改科室 2. 医生管理&#xff1a;录入医生信息以及科室信息&#xff0c;修改医生信息&#xff08;主要是修改个人…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...