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的操作,还附带了手写模拟器这样的效果只是在使用的时候不仅需要手动下载字体,而且效果也并不是太理想。 今天小编找到了一款软件--手写模拟器,不仅一键生…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...