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

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英文全称&#xff1a;Android annotation process tool是一种处理注释的工具&#xff0c;它对源代码文件进行检测找出其中的Annotation&#xff0c;使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文…...

线程的基本概念

文章目录基础概念线程与进程什么是进程&#xff1f;什么是线程&#xff1f;进程和线程的区别&#xff1a;多线程什么是多线程&#xff1f;多线程的局限性串行、并行、并发同步异步、阻塞非阻塞线程的创建1、继承Thread类&#xff0c;重写run方法2、实现Runnable接口&#xff0c…...

java面试题中常见名词注解

一.常见名词注解 1.mysql索引&#xff0c;索引数据结构&#xff0c;hash&#xff0c;二叉树&#xff0c;B树&#xff0c;B树&#xff0c;红黑树&#xff0c; mysql索引&#xff1a;帮助mysql高效获取数据的数据结构&#xff0c;通俗来说&#xff0c;数据库索引就好比一本书的…...

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(二)

文章目录系列文档索引四、Spring AOP的使用入门1、激活AspectJ模块&#xff08;1&#xff09;注解激活&#xff08;2&#xff09;XML激活2、创建 AspectJ 代理&#xff08;了解&#xff09;&#xff08;1&#xff09;编程方式创建 AspectJ 代理实例&#xff08;2&#xff09;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模块。在存储模块中&#xff0c;提到了etcd kv存储backend是基于boltdb实现的&#xff0c;其在boltdb的基础上封装了读写事务&#xff0c;通过内存缓存批量将事务刷盘&#xff0c;提升整体的写入性能。botldb是etcd的真正的底层存储。本…...

基于MATLAB开发AUTOSAR软件应用层模块-part21.SR interface通信介绍(包括isupdated判断通信)

这篇文章我们介绍最后一种interface,即Sender-Receiver Interface,这种通信方式是autosar架构中最常用的的通信方式,即一个SWC发送数据,另一个SWC接收数据,实现数据交互。下边我们介绍下这篇文章主要介绍的内容: 目录如下: 如何配置SR interface,实现SR 通信介绍含有…...

Kotlin新手教程八(泛型)

一、泛型 1.泛型类的创建与实例化 kotlin中泛型类的创建与实例化与Java中相似&#xff1a; class A<T>(t:T){var valuet }fun main() {var a:A<Int> A<Int>(11) }Kotlin中存在类型推断&#xff0c;所以创建实例可以写成&#xff1a; var aA(11)2.泛型约束…...

性能测试知多少?怎样开展性能测试

看到好多新手&#xff0c;在性能需求模糊的情况下&#xff0c;随便找一个性能测试工具&#xff0c;然后就开始进行性能测试了&#xff0c;在这种情况下得到的性能测试结果很难体现系统真实的能力&#xff0c;或者可能与系统真实的性能相距甚远。 与功能测试相比&#xff0c;性能…...

code-breaking之javacon

JAVACON 题目 此题 来自P神 的code-breaking中的一道Java题&#xff0c;名为javacon&#xff0c;题目知识点为SpEL注入 题目下载地址&#xff1a;https://www.leavesongs.com/media/attachment/2018/11/23/challenge-0.0.1-SNAPSHOT.jar 运行环境 java -jar challenge-0.…...

Android 字符串替换,去除空格等操作

今天在写代码的时候&#xff0c;需要对String进行一些操作&#xff0c;例如变小写&#xff0c;去除所有空格 于是熟练的使用String的replaceAll&#xff0c;却发现没这个方法。 后面才发现Kotlin使用的是自己的String&#xff0c;有自己的方法&#xff0c;用String的replace(…...

因“AI”而“深” 第四届OpenI/O 启智开发者大会高校开源专场25日开启!

中国算力网资源不断开发&#xff0c;开源社区治理及AI开源生态引来众多有才之士参与建设&#xff0c;国家级开放创新应用平台、NLP大模型等高新技术内容逐渐走向科研舞台上聚光灯的中心&#xff0c;新时代的大门缓缓打开。在启智社区&#xff0c;有一群人&#xff0c;他们年纪轻…...

CATCTF wife原型链污染

CATCTF wife原型链污染 原型链污染原理&#xff1a;https://drun1baby.github.io/2022/12/29/JavaScript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/ 如下代码&#xff0c;prototype是newClass类的一个属性。newClass 实例化的对象 newObj 的 .__proto__ 指向 newClass…...

浅谈Java线程池中的ThreadPoolExecutor工具类

目录 ThreadPoolExecutor的构造函数 关于线程池的一些补充 线程池运行原理分析 概念原理解释 整个流程图如下&#xff1a; 一点补充 创建线程池主要有两种方式&#xff1a; 通过Executor工厂类创建&#xff0c;创建方式比较简单&#xff0c;但是定制能力有限通过ThreadPoo…...

分析过程:服务器被黑安装Linux RootKit木马

前言 疫情还没有结束&#xff0c;放假只能猫家里继续分析和研究最新的攻击技术和样本了&#xff0c;正好前段时间群里有人说服务器被黑&#xff0c;然后扔了个样本在群里&#xff0c;今天咱就拿这个样本开刀&#xff0c; 给大家研究一下这个样本究竟是个啥&#xff0c;顺便也给…...

运动型蓝牙耳机推荐哪款、最新运动蓝牙耳机推荐

提起运动耳机&#xff0c;如今很多运动爱好者和职业教练员们&#xff0c;都会向萌新推荐骨传导运动耳机。骨传导耳机解决了入耳式蓝牙耳机掉落的问题&#xff0c;佩戴相当舒服。骨传导耳机在佩戴过程中解放了双耳&#xff0c;不会因为耳机堵住耳朵&#xff0c;听不到环境音&…...

Python爬虫(9)selenium爬虫后数据,存入mongodb实现增删改查

之前的文章有关于更多操作方式详细解答&#xff0c;本篇基于前面的知识点进行操作&#xff0c;如果不了解可以先看之前的文章 Python爬虫&#xff08;1&#xff09;一次性搞定Selenium(新版)8种find_element元素定位方式 Python爬虫&#xff08;2&#xff09;-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生命周期钩子以及使用方式

按照生命周期的顺序来排列&#xff1a; onBeforeMount() DOM挂载前调用 注册一个钩子&#xff0c;在组件被挂载之前被调用。 当这个钩子被调用时&#xff0c;组件已经完成了其响应式状态的设置&#xff0c;但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。 onMount(…...

以假乱真的手写模拟器?

前些时候给大家推荐了一款word插件叫做“不坑盒子”&#xff0c;这款盒子不仅方便了word的操作&#xff0c;还附带了手写模拟器这样的效果只是在使用的时候不仅需要手动下载字体&#xff0c;而且效果也并不是太理想。 今天小编找到了一款软件--手写模拟器&#xff0c;不仅一键生…...

每日一题——L1-069 胎压监测(15)

L1-069 胎压监测 分数 15 小轿车中有一个系统随时监测四个车轮的胎压&#xff0c;如果四轮胎压不是很平衡&#xff0c;则可能对行车造成严重的影响。 让我们把四个车轮 —— 左前轮、右前轮、右后轮、左后轮 —— 顺次编号为 1、2、3、4。本题就请你编写一个监测程序&#…...

17_FreeRTOS事件标志组

目录 事件标志组 事件标志组与队列、信号量的区别 事件标志组相关API函数介绍 实验源码 事件标志组 事件标志位:用一个位,来表示事件是否发生 事件标志组是一组事件标志位的集合,可以简单的理解事件标志组,就是一个整数。 事件标志组的特点: 它的每一个位表示一个事件(…...

美团前端常考手写面试题总结

实现观察者模式 观察者模式&#xff08;基于发布订阅模式&#xff09; 有观察者&#xff0c;也有被观察者 观察者需要放到被观察者中&#xff0c;被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式&#xff0c;收集观察者&#xff0c;状态变化后要主动通知观…...

MyBatis基于XML的详细使用——动态sql

目录 动态sql if where trim foreach choose、when、otherwise set bind sql MyBatis常用OGNL表达式 动态sql 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架&#xff0c;你应该能理解根据不同条件拼接 SQL 语句有多痛苦&#xff0c;例如…...

CMake编译opencv4.6

openCV系列文章目录 文章目录openCV系列文章目录前言一、准备工作二、使用步骤1.使用CMake编译openCV总结前言 最近在项目中遇到图片处理&#xff0c;一拍脑袋就想到大名鼎鼎的opencv 一、准备工作 1.openCV官网下载 2.CMake官方下载 3.vs2019官方下载 二、使用步骤 1.使用…...

数据分片(mycat)

1. 数据分片概念&#xff1a; 1.1. 分库分表 什么是分库分表&#xff1a; 将存放在一台数据库服务器中的数据&#xff0c;按照特定方式&#xff08;指的是程序开发的算法&#xff09;进行拆分&#xff0c;分散存放到多台数据库服务器中&#xff0c;以达到分散单台服务器负载的…...

基于STM32设计的倒车雷达系统(超声波模块多方位测距应用)

一、项目背景 汽车高科技产品家族中,专为倒车泊位设置的“倒车雷达”应运而生,倒车雷达的加装可以解决驾驶人员的后顾之忧,大大降低到车事故的发生。汽车倒车雷达全称为“倒车防撞雷达”,也叫“泊车辅助装置”,是汽车泊车安全辅助装置,能以声音或者更为直观的显示来告知…...

Robot Framework + Selenium2Library环境下,结合Selenium Grid实施分布式自动化测试

最近一段时间&#xff0c;公司在推行自动化测试流程&#xff0c;本人有幸参与了自定义通用控件的关键字封装和脚本辅助编写、数据驱动管理、测试用例执行管理等一系列工具软件的研发工作&#xff0c;积累了一些经验&#xff0c;在此与大家做一下分享&#xff0c;也算是做一个总…...

洛谷——前缀和与差分

前缀和与差分 文章目录前缀和与差分应用总结前缀和截断数组思路代码最大加权矩形题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路代码差分海底高铁题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路代码改变数组元素思路代码应用总结 前缀和用来…...

离线内网环境部署更新问题记录

文章目录低级错误错误一 配置文件参数错误错误二 文件位置错误新遇到的错误其他遇到的问题经验教训低级错误 错误一 配置文件参数错误 在与现场实施人员沟通时&#xff0c;出现信息错位&#xff0c;实施人员发来的截图里的ip地址不是正在使用的ip地址&#xff08;机器c重装系…...