Android架构设计——【 APT技术实现butterknife框架 】
APT简介
APT英文全称:Android annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。
Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。简言之:APT可以把注解,在编译时生成代码。
是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。
简单说:根据规则,帮我们生成代码、生成类文件
编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife、Dagger、ARouter 等 ButterKnife 原理分析使用APT来处理
annotation的流程
- 1.定义注解(如@MyButterKnife)
- 2.定义注解处理器
- 3.在处理器里面完成处理方式,通常是生成java代码。
- 4.注册处理器
- 5.利用APT完成如下图的工作内容。

手写ButterKnife
想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。
准备工作
(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。 注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲

(2)创建第二个java library取名为annotation_process,故名思议此module是存放注解处理器。

(3)为了简单起见,此处就在annotaiton中创建两个注解BindView和OnClick,分别作用于编译期,因此定于为CLASS。
BindView作用:初始化View成员变量,替开发者实现findViewById。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {int value();
}
OnClick作用:初始化Button等按钮,替用户实现setOnClickListener等等。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {int[] value();
}
(4)导入项目依赖
app module依赖annotation和annotation_process

anontation_process依赖annotation。从此处可以说明为什么annotation和annotation_process必须定义为java library,android工程可以依赖java工程,但反之不行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sdm1iXSz-1676966714695)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/538be825bf864a1ea7b45d5379fd6519~tplv-k3u1fbpfcp-zoom-1.image)]
(5)在annotation_process的build.grade中添加以下两句依赖用于实现注解处理器。
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'

定义注解处理器
1、继承AbstractAnnotation, 2、在其类上添加@AutoService 3、实现process方法
@AutoService(Process.class)
public class AnnotationProcess extends AbstractProcessor {//通过io流动态生成代码private Filer filer;
@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);filer = processingEnvironment.getFiler();}@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> set = new HashSet<>();set.add(BindView.class.getName());set.add(OnClick.class.getName());return set;}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//将Activity和其内部所有被OnCLick+BindView标记了的元素一一对应起来HashMap<TypeElement, ElementForType> hashMap = findAnnotationForActivity(roundEnvironment);//写文件if (hashMap.size() != 0) {Writer writer = null;Iterator<TypeElement> iterator = hashMap.keySet().iterator();while (iterator.hasNext()) {TypeElement element = iterator.next();ElementForType elementForType = hashMap.get(element);//获取Activity类名String className = element.getSimpleName().toString();//生成新类名String newClassName = className + "$$ButterKnife";//获取包名PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(element);String packageName = packageElement.getQualifiedName().toString();JavaFileObject javaFileObject = null;try {javaFileObject = filer.createSourceFile(packageName + "." + newClassName);writer = javaFileObject.openWriter();//将所有要生成的代码存到StringBuffer中StringBuffer stringBuffer = getStringBuffer(packageName, newClassName, element, elementForType);} catch (IOException e) {e.printStackTrace();}finally {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}return false;}
public StringBuffer getStringBuffer(String packageName, String newClazzName,TypeElement typeElement, ElementForType elementForType) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("package " + packageName + ";\n");stringBuffer.append("import android.view.View;\n");stringBuffer.append("public class " + newClazzName + "{\n");stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) {List<VariableElement> viewElements = elementForType.getViewElements();for (VariableElement viewElement : viewElements) {//获取到类型TypeMirror typeMirror = viewElement.asType();//获取到控件的名字Name simpleName = viewElement.getSimpleName();//获取到资源IDint resId = viewElement.getAnnotation(BindView.class).value();stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\n");}}if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) {List<ExecutableElement> methodElements = elementForType.getMethodElements();for (ExecutableElement methodElement : methodElements) {int[] resIds = methodElement.getAnnotation(OnClick.class).value();String methodName = methodElement.getSimpleName().toString();for (int resId : resIds) {stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() {\n");stringBuffer.append("public void onClick(View p0) {\n");stringBuffer.append("target." + methodName + "(p0);\n");stringBuffer.append("}\n});\n");}}}stringBuffer.append("}\n}\n");return stringBuffer;}
private HashMap<TypeElement, ElementForType> findAnnotationForActivity(RoundEnvironment roundEnvironment) {HashMap<TypeElement, ElementForType> hashMap = new HashMap<>();//获取到所有被BindView标记了的View元素Set<? extends Element> bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获取所有被OnClick标记了的Method元素Set<? extends Element> onClickSet = roundEnvironment.getElementsAnnotatedWith(OnClick.class);for (Element element : bindViewSet) {VariableElement bindView = (VariableElement) element;//获取含有该BindView标记对应的ActivityTypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取该Activity中所有被注解标记的元素ElementForType elementForType = hashMap.get(typeElement);List<VariableElement> variableElementList;if (elementForType != null) {//获取该Activity中所有被BindView标记的View元素variableElementList = elementForType.getViewElements();if (variableElementList == null) {variableElementList = new ArrayList<>();elementForType.setViewElements(variableElementList);}} else {//若ElementForType不存在则创建elementForType = new ElementForType();variableElementList = new ArrayList<>();elementForType.setViewElements(variableElementList);//判断hashmap中是否保存了elementForType和typeElementif (!hashMap.containsKey(typeElement)) {hashMap.put(typeElement, elementForType);}}variableElementList.add(bindView);}//onclick同理for (Element element : onClickSet) {ExecutableElement onClick = (ExecutableElement) element;//获取含有该OnClick标记对应的ActivityTypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取该Activity中所有被注解标记的元素ElementForType elementForType = hashMap.get(typeElement);List<ExecutableElement> executableElementList;if (elementForType != null) {//获取该Activity中所有被OnClick标记的Method元素executableElementList = elementForType.getMethodElements();if (executableElementList == null) {executableElementList = new ArrayList<>();elementForType.setMethodElements(executableElementList);}} else {//若ElementForType不存在则创建elementForType = new ElementForType();executableElementList = new ArrayList<>();elementForType.setMethodElements(executableElementList);//判断hashmap中是否保存了elementForType和typeElementif (!hashMap.containsKey(typeElement)) {hashMap.put(typeElement, elementForType);}}executableElementList.add(onClick);}return hashMap;}
}
ButterKnife在编译阶段会为每个使用了BindView、OnClick等注解的类生成一个新类,因此创建了ElementForType用以封装同一个Activity中的所有被BindView+OnCLick等注解的元素。
/*** 此类用以封装同一个Activity中的所有被BindView+OnCLick注解的元素*/
public class ElementForType {//此出的VariableElement代表的控件元素private List<VariableElement> viewElements;//此处的ExecutableElement代表的方法元素private List<ExecutableElement> methodElements;
public ElementForType() {}public ElementForType(List<VariableElement> viewElements, List<ExecutableElement> methodElements) {this.viewElements = viewElements;this.methodElements = methodElements;}public List<VariableElement> getViewElements() {return viewElements;}public void setViewElements(List<VariableElement> viewElements) {this.viewElements = viewElements;}public List<ExecutableElement> getMethodElements() {return methodElements;}public void setMethodElements(List<ExecutableElement> methodElements) {this.methodElements = methodElements;}
}
定义ButterKnife
由于APT技术是在编译期替我们生成的一个新类,因此需要注意的是ButterKnife.java中的newClassName类名必须要和AnnotationProcess.java中的newClassName一致,否则无法生效。
public class ButterKnife {public static void bind(Context context){//获取类名String className = context.getClass().getName();//获取生成的类的构造器String newClassName = className + "$$ButterKnife";Constructor<?> constructor = null;try {constructor = Class.forName(newClassName).getConstructor();constructor.newInstance(context);} catch (Exception e) {e.printStackTrace();}}
}
使用Butterknife
使用和ButterKnife一样即可
public class MainActivity extends AppCompatActivity {@BindView(R.id.btn1)private Button btn1;@BindView(R.id.btn2)private Button btn2;@BindView(R.id.btn3)private Button btn3;@BindView(R.id.text)private EditText text;
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);text.setText("xixi");}@OnClick({R.id.btn1,R.id.btn2,R.id.btn3})public void onClick(View view){switch (view.getId()){case R.id.btn1:Log.d("kkjj","按钮1");break;case R.id.btn2:Log.d("kkjj","按钮1");break;case R.id.btn3:Log.d("kkjj","按钮1");break;}}
}

上文解析了Android架构设计技能中;APT技术实现butterknife;更多的Android框架学习及高级Android进阶;可参考《Android核心技术手册》里面内容包含上千个技术知识点。点击查看》》》
结
BUtterKnife通过注解+注解处理器的方式在编译期动态地生成XXX$$ButterKnife.java文件,在此文件中替开发者实现了findViewById、setOnClickListener等等操作。
所以ButterKnife等三方框架仍然通过findViewById、setOnClickListener操作实现的view绑定,监听注册。因此并没有绕过Android原生的开发规则。
相关文章:
Android架构设计——【 APT技术实现butterknife框架 】
APT简介 APT英文全称:Android annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文…...
线程的基本概念
文章目录基础概念线程与进程什么是进程?什么是线程?进程和线程的区别:多线程什么是多线程?多线程的局限性串行、并行、并发同步异步、阻塞非阻塞线程的创建1、继承Thread类,重写run方法2、实现Runnable接口,…...
java面试题中常见名词注解
一.常见名词注解 1.mysql索引,索引数据结构,hash,二叉树,B树,B树,红黑树, mysql索引:帮助mysql高效获取数据的数据结构,通俗来说,数据库索引就好比一本书的…...
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(二)
文章目录系列文档索引四、Spring AOP的使用入门1、激活AspectJ模块(1)注解激活(2)XML激活2、创建 AspectJ 代理(了解)(1)编程方式创建 AspectJ 代理实例(2)XM…...
华为OD机试 - 斗地主(C++) | 附带编码思路 【2023】
刷算法题之前必看 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12199283.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 华为OD机试题…...
【存储】etcd的存储是如何实现的(3)-blotdb
前两篇分别介绍了etcd的存储模块以及mvcc模块。在存储模块中,提到了etcd kv存储backend是基于boltdb实现的,其在boltdb的基础上封装了读写事务,通过内存缓存批量将事务刷盘,提升整体的写入性能。botldb是etcd的真正的底层存储。本…...
基于MATLAB开发AUTOSAR软件应用层模块-part21.SR interface通信介绍(包括isupdated判断通信)
这篇文章我们介绍最后一种interface,即Sender-Receiver Interface,这种通信方式是autosar架构中最常用的的通信方式,即一个SWC发送数据,另一个SWC接收数据,实现数据交互。下边我们介绍下这篇文章主要介绍的内容: 目录如下: 如何配置SR interface,实现SR 通信介绍含有…...
Kotlin新手教程八(泛型)
一、泛型 1.泛型类的创建与实例化 kotlin中泛型类的创建与实例化与Java中相似: class A<T>(t:T){var valuet }fun main() {var a:A<Int> A<Int>(11) }Kotlin中存在类型推断,所以创建实例可以写成: var aA(11)2.泛型约束…...
性能测试知多少?怎样开展性能测试
看到好多新手,在性能需求模糊的情况下,随便找一个性能测试工具,然后就开始进行性能测试了,在这种情况下得到的性能测试结果很难体现系统真实的能力,或者可能与系统真实的性能相距甚远。 与功能测试相比,性能…...
code-breaking之javacon
JAVACON 题目 此题 来自P神 的code-breaking中的一道Java题,名为javacon,题目知识点为SpEL注入 题目下载地址:https://www.leavesongs.com/media/attachment/2018/11/23/challenge-0.0.1-SNAPSHOT.jar 运行环境 java -jar challenge-0.…...
Android 字符串替换,去除空格等操作
今天在写代码的时候,需要对String进行一些操作,例如变小写,去除所有空格 于是熟练的使用String的replaceAll,却发现没这个方法。 后面才发现Kotlin使用的是自己的String,有自己的方法,用String的replace(…...
因“AI”而“深” 第四届OpenI/O 启智开发者大会高校开源专场25日开启!
中国算力网资源不断开发,开源社区治理及AI开源生态引来众多有才之士参与建设,国家级开放创新应用平台、NLP大模型等高新技术内容逐渐走向科研舞台上聚光灯的中心,新时代的大门缓缓打开。在启智社区,有一群人,他们年纪轻…...
CATCTF wife原型链污染
CATCTF wife原型链污染 原型链污染原理:https://drun1baby.github.io/2022/12/29/JavaScript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/ 如下代码,prototype是newClass类的一个属性。newClass 实例化的对象 newObj 的 .__proto__ 指向 newClass…...
浅谈Java线程池中的ThreadPoolExecutor工具类
目录 ThreadPoolExecutor的构造函数 关于线程池的一些补充 线程池运行原理分析 概念原理解释 整个流程图如下: 一点补充 创建线程池主要有两种方式: 通过Executor工厂类创建,创建方式比较简单,但是定制能力有限通过ThreadPoo…...
分析过程:服务器被黑安装Linux RootKit木马
前言 疫情还没有结束,放假只能猫家里继续分析和研究最新的攻击技术和样本了,正好前段时间群里有人说服务器被黑,然后扔了个样本在群里,今天咱就拿这个样本开刀, 给大家研究一下这个样本究竟是个啥,顺便也给…...
运动型蓝牙耳机推荐哪款、最新运动蓝牙耳机推荐
提起运动耳机,如今很多运动爱好者和职业教练员们,都会向萌新推荐骨传导运动耳机。骨传导耳机解决了入耳式蓝牙耳机掉落的问题,佩戴相当舒服。骨传导耳机在佩戴过程中解放了双耳,不会因为耳机堵住耳朵,听不到环境音&…...
Python爬虫(9)selenium爬虫后数据,存入mongodb实现增删改查
之前的文章有关于更多操作方式详细解答,本篇基于前面的知识点进行操作,如果不了解可以先看之前的文章 Python爬虫(1)一次性搞定Selenium(新版)8种find_element元素定位方式 Python爬虫(2)-Selenium控制浏览…...
gulimall技术栈笔记
文章目录1.项目背景1.1电商模式1.2谷粒商城2.项目架构图3.项目技术&特色4.项目前置要求5.分布式基础概念5.1微服务5.2集群&分布式&节点5.3远程调用5.4负载均衡5.5服务注册/发现&注册中心5.6配置中心5.7服务熔断&服务降级5.7.1服务熔断5.7.2服务降级5.8API网…...
vue3生命周期钩子以及使用方式
按照生命周期的顺序来排列: onBeforeMount() DOM挂载前调用 注册一个钩子,在组件被挂载之前被调用。 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。 onMount(…...
以假乱真的手写模拟器?
前些时候给大家推荐了一款word插件叫做“不坑盒子”,这款盒子不仅方便了word的操作,还附带了手写模拟器这样的效果只是在使用的时候不仅需要手动下载字体,而且效果也并不是太理想。 今天小编找到了一款软件--手写模拟器,不仅一键生…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
