Android架构设计——【 APT技术实现butterknife框架 】

news2025/2/28 6:53:31

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;
​
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
    }
    
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }
    
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(BindView.class.getName());
        set.add(OnClick.class.getName());
        return set;
    }
    
    @Override
    public 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();
                //获取到资源ID
                int 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标记对应的Activity
            TypeElement 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和typeElement
                if (!hashMap.containsKey(typeElement)) {
                    hashMap.put(typeElement, elementForType);
                }
            }
            variableElementList.add(bindView);
        }
    
        //onclick同理
        for (Element element : onClickSet) {
            ExecutableElement onClick = (ExecutableElement) element;
            //获取含有该OnClick标记对应的Activity
            TypeElement 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和typeElement
                if (!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;
​
    @Override
    protected 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原生的开发规则。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

100种思维模型之启发式偏差思维模型-017

曾国藩在给儿子的一封家书中曾写道&#xff1a;余于凡事皆用困知勉行工夫&#xff0c;尔不可求名太骤&#xff0c;求效太捷也。熬过此关&#xff0c;便可少进。再进再困&#xff0c;再熬再奋&#xff0c;自有亨通精进之日。 不急躁不求捷径&#xff0c;小火慢炖&#xff0c;将事…

burp小程序抓包

身为一名码农&#xff0c;抓包肯定是一项必备技能。工作中遇到很多次需要对小程序进行抓包排查问题。下面分享一下我的抓包方式&#xff0c;使用的是电脑版小程序抓包&#xff0c;跟手机的方式都差不多的。 一、环境 微信版本&#xff1a;3.6.0.18 Burpsuite版本&#xff1a…

Python容器

容器 容器是一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素&#xff0c;每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。 数据容器根据特点的不同&#xff0c;如&#xff1a; 是否支持重复元素是否可以修改是否…

k8s1.17.2+centos7.7+docker18.06

1.简介 1.1pod网络 总述&#xff1a;Kubernetes 的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中 Flannel&#xff1a;基于L2&#xff0c;构建用于各个pod通信的网络架构。使用iptables进行数据包过滤。Calico&#xff1a;纯L3&#xff0c;构建用于各个pod通…

【Java基础】反射

概述 引入 package ref;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.r…

VUCA项目环境中如何提升项目进度计划与控制工作有效性的系统思考【一杯咖啡谈项目】

VUCA环境下&#xff0c;项目进度延迟是经常遇见的问题&#xff0c;如何解决此类问题&#xff1f;今天分享一个案例&#xff0c;在这个案例中&#xff0c;基于“根因分析法”&#xff0c;对某主题客户端项目的进度延迟问题进行了分析&#xff0c;找到根本原因&#xff0c;提出了…

鼠标指针文件格式解析

鼠标指针文件格式解析 文章目录鼠标指针文件格式解析windowsico文件格式分析文件头&#xff1a;图像数据头段&#xff1a;图像数据段&#xff1a;Ani动态光标格式解析数据结构&#xff1a;anihseq **rate**LISTcur静态光标文件格式解析macOSLinuxwindows ico文件格式分析 是一…

2023年PMP考试复习攻略

如何备考PMP呢&#xff1f; 这本书是PMP认证考试的官方教材&#xff0c;体系完善&#xff0c;可以迅速帮助入门者搭建项目管理知识体系&#xff0c;备考PMP考试的伙伴&#xff0c;这本书一定要读一遍&#xff01; 经验都是积累出来的&#xff0c;交流小队里有很多分享面试经验…

Vue3 中生命周期的使用

目录前言&#xff1a;一、什么是生命周期函数二、什么是 Keep-Alive 组件三、生命周期函数分为几种&#xff0c;分别有什么用&#xff1f;1. beforeCreate2. created3. beforeMount/onBeforeMount4. mounted/onMounted5. beforeUpdate/onBeforeUpdate6. updated/onUpdated7. be…

spring之事务概述

文章目录前言一、事务概述1、什么是事务2、事务的四个处理过程3、事务的四个特性二、引入事务场景1、引入依赖2、数据库创建3、建包4、spring.xml配置文件5、测试程序6、运行结果&#xff08;成功&#xff09;7、模拟异常三、Spring对事务的支持1、Spring实现事务的两种方式2、…

数值方法笔记4:插值、近似和拟合

1. 插值1.1 插值的一些概念1.1.1 插值的定义1.1.2 插值的存在性1.1.3 插值的误差分析1.2 拉格朗日插值(Lagrange Interpolation)1.2.1 拉格朗日插值误差分析1.3 Newton多项式插值1.3.1 Newton多项式插值误差分析1.4 Chebyshev多项式确定插值点1.4.1 Chebyshev多项式性质1.5 有理…

Green Hills Software(GHS)的安装

Green Hills Software(GHS)简介 Green Hills Software(GHS)是美国Green Hills软件公司提供的一种具有调试、编译器和闪存编程工具的集成开发环境,是汽车电子行业常用且重要的开发环境之一。它支持的功能包括:AUTOSAR感知、项目构建器、代码覆盖、运行时错误检查、MISRA C…

【HEC-RAS水动力】HEC-RAS 1D基本原理(恒定流及非恒定流)

一、数据说明 HEC-RAS模型主要由工程文件 (.prj) 文 件 、 河道地形数据文件 ( .g01)、运行文件(p01)、非恒定流文件 ( .u01) 等部分组成。 1. 一般数据 在创建并保存project文件(*.prj)后,其他data文件均会自动以同样的名字保存,但采用不同的后缀来区分各类文件。 &qu…

网络编程之IP 地址的介绍

IP 地址的介绍学习目标能够说出IP 地址的作用1. IP 地址的概念IP 地址就是标识网络中设备的一个地址&#xff0c;好比现实生活中的家庭地址。网络中的设备效果图:2. IP 地址的表现形式说明:IP 地址分为两类&#xff1a; IPv4 和 IPv6IPv4 是目前使用的ip地址IPv6 是未来使用的i…

Redis进阶-缓存问题

Redis 最常用的一个场景就是作为缓存&#xff0c;本文主要探讨Redis作为缓存&#xff0c;在实践中可能会有哪些问题&#xff1f;比如一致性、击穿、穿透、雪崩、污染等。 为什么要理解Redis缓存问题 在高并发业务场景下&#xff0c;数据库大多数情况都是用户并发访问最薄弱的…

计算机是怎么读懂C语言的?

文章目录前言程序环境翻译环境翻译环境分类编译预处理预处理符号预定义符号#define#undef命令行定义条件编译文件包含头文件包含查找规则嵌套文件包含其他预处理指令编译阶段汇编链接&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;博主大一智能制造在读&#xff…

Linux 目录操作命令

目录操作命令 进入目录 cd命令 cd 目录名 进入指定目录 cd / 进入根目录 cd … 返回上级目录 cd - 返回上一次所在的目录 cd ~ 进入当前用户的家目录 cd /usr 进入指定目录&#xff0c;绝对路径 创建目录 mkdir 目录名 在usr目录下创建dd目录 进入目录 cd 目录名 进入us…

TypeScript 学习笔记

最近在学 ts 顺便记录一下自己的学习进度&#xff0c;以及一些知识点的记录&#xff0c;可能不会太详细&#xff0c;主要是用来巩固和复习的&#xff0c;会持续更新 前言 想法 首先我自己想说一下自己在学ts之前&#xff0c;对ts的一个想法和印象&#xff0c;在我学习之前&a…

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

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

适合打游戏用的蓝牙耳机有哪些?吃鸡无延迟的蓝牙耳机推荐

现在手游的兴起&#xff0c;让游戏市场变得更加火爆&#xff0c;各种可以提高玩家体验的外设也越来越多&#xff0c;除了提升操作的外置按键与手柄外&#xff0c;能带来更出色音质与舒心使用的游戏耳机&#xff0c;整体氛围感更好&#xff0c;让玩家在细节上占据优势&#xff0…