基本使用:

对于butterknife库我想基本上都非常熟了,如今在项目中用它也用得非常之频繁了,不过为了学习的完整性,先来简单的回顾一下基本用法,先新建一个工程:

然后给textview增加一个点击事件,做个超简单的事:

运行效果:

这是我们通常的做法,而有了butterknife之后,则使用会更加的简单,解决的其实就是findViewById这个比较机械式又不得不写的代码,下面简单来用一下butterknife,一切原理的剖析都需要建立在会用的基础之上,先上一下它的官网:

不过多解释,先来将其集成到工程中来使用一下:

编译报错了。。

Manifest merger failed : Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:5:5-19:19 to override.

其实这个错误就是我们的项目的某些属性和第三方库中的属性有冲突时或者我们想修改第三方库中某些资源的属性时,我们就需要使用tools:replace来处理。其实最新版的butterknife在androidX上会一些冲突,按建立来弄也会报其它错,所以简单起见降一下版本,这里采用这个版本:

编译:

butterknife里面使用了lambda,所以需要指定一下JDK版本才行,如下:

好接下来则使用一下butterknife,如下:

使用比较简单,不多说,接下来重点就是我们自己从0开始来自己实现一个这样的功能来了解butterknife的原理机制。

利用反射自己来实现:

接下来咱们新建一个Library:

然后我们将butterknife的依赖给去掉,因为我们要自己来实现这样的效果:

此时肯定会报错了:

咱们先将点击事件的报错给去掉,先实现@BindView的功能:

然后咱们在我们的library中新建一个注解BindView:

然后咱们再来新建一个ButterKnife类,如下:

然后我们的主工程依赖一下我们新建的这个library,如下:

然后导一下我们自己的包,就不会报错了:

目前BindView这个注解还没加限定所以报错了,关于注解第一个需要配置的是它的存活周期,如下:

其中可以指定如下选项:

咱们这里用第三个,因为在运行时需要进行反馈的,所以指定一下:

接下来需要指定一个target,如下:

看一下它可以指定哪些选项:

指定它只能加在成员变量上之后,我们如果将此注解加在非成员变量上则会报错的,比如放在类上,如下:

其它注解是一个特殊的接口,所以里面可以定义方法,下面来学习下它的基本用法,咱们先定义一个方法:

此时就可以这样用了:

再增加一个方法:

其中还可以有默认值,如下:

这样我们在调用时不传name也不会报错,因为有默认值了,如下:

另外还有一个小细节,对于Butterknife而言,貌似写法并没有xxx=,如下:

其实是因为对于注解来说,如果定义的唯一的方法名是value,其赋值就可以省略了,如下:

此时的调用写法就跟butterknife一模一样啦,如下:

至此,注解就定义好了,接下来则需要对其注解进行处理,也就是它:

为了更加专注,将调用程序改得更加简单一些,完全不要点击事件,专心实现findViewById功能,如下:

好,焦点就回到了ButterKnife的bind()方法了,肯定得要用反射技术了,实现比较简单,如下:

这就是利用反射对butterknife的简单实现,当然这是最初级的版本,但是能看到butterknife的一些原理。

利用AnnotationProcessor自己来实现【高仿butterknife】:

1、将查找View代码拆到新类MainActivityBinding,并ButterKnife.bind()方法利用反射来调用该类。

接下来咱们再新建一个module,来还原一下真正butterknife是如何实现的,如下:

而回顾一下利用反射的方式,其实是自己做了findViewById了,如下:

接下来为了往ButterKnife的原理靠,需要转换一下思路,先去掉这个反射库的依赖:

此时肯定会报错:

咱们这时依赖一下我们新建的lib:

然后在里面新建一个ButterKnife类,里面的bind()方法先空实现:

此时重新导一下包之后就只有注解这块飘红了:

咱们第一步先不用这个注解,一步步来变成ButterKnife真正的样子,所以代码会变成:

接下来就是得实现bind()方法了,这里的思路转变是此bind()里面的findView的过程放到另外一个类,而这里面通过反射去调用它来,具体做法如下,新建一个类,注意是在主功程里面:

然后里面只定义一个构造方法,并且构造方法中有初始化view的代码,具体如下:

就是这么简单直接,这里不采用反射方式了,毕境反射还是有性能问题,那此类新建的目的是为了啥呢?其实就是将来要将这个类动态生成,而不用人为去手动编写到,然后在ButterKnife.bind()方法去通过反射来调用此类,具体形态先手动模拟一下:

其中,这里涉及到一个貌似不怎么常用的API:

它跟getName()很类似:

当有内部类的时候这俩显示就有区别了,下面实验一下:

所以将测试代码还原,还是回到bind()方法继续来编写:

比较容易理解,也就是用反射来调用MainActivityBinding这个未来要通过自动生成的查找View的类,运行发现一切都正常,目前这是ButterKnife的雏形。

2、想办法利用annotationprocessor来自动生成MainActivityBinding:

接下来首先得要了解annotationprocessor的用法,所以需要再新建一个library,不过这里只需要Java Library既可,不需要Android Library了,因为它只是用来生成代码用的,如下:

注意这里用的是annotionProcessor来进行依赖了,接着还得在这个lib里面按一定的格式来新建文件,固定规则,如下:

然后再建一个子目录,如下:

再建一个目录:

然后再建一个文件:

然后将我们的BindProcessor文件的全路径在这个文件中进行声明,如下:

这样注册的目的就是告诉gradle,由于在工程中有这个依赖:

所以它就会找lib-processor这个项目中的指定目录下注册的BindProcessor,所以接下来就是专心来编写BindProcessor,首先需要继承一个类:

这样就可以在编译阶段来根据我们编写的processor的代码来生成指定的代码。首先来写这个方法:

但是!!如今gradle依赖上有个这样的问题,先来挼一挼:

那最好的办法就再新建一个Java Library专门定义注解然后共享给它们,所以再新建一个:

然后再来定义该注解,首先声明其使用范围,还记得用反射来实现时它的定义么?

但是!!咱们此时需要改一改了,因为只需要在编译阶段中看到,在运行阶段是不需要看见的,所以更改如下:

接下来定义一下target:

然后再定义一个方法:

好,接下来定义好依赖关系:

所以在lib-processor中添加对lib-annotations的依赖:

接下来继续:

所以我们的主功能也需要添加对lib-annotations的依赖,但是由于主功能已经依赖于lib-annatationprocessor,如下:

因为我们写的library是要被用户使用的,能尽量让用户少加一个依赖就尽量不用,所以此时可以将此依赖放到lib-annotationprocessor来了,但是此时只要用一个传递依赖既可:

好,此时依赖关系就定义好了,比较容易混迷糊,下面用图来对目前的依赖关系简单画一下:

挼清依赖关系之后,接下来来处理我们的processor逻辑:

好,接下来再来实现它里面关键的process()方法,在正式编码之前先来打印一下,看是否在编译时执行了:

然后在编译之前得在应用层先使用一下注解才行,这样才能扫描到让我们的processor发挥作用,如下:

gradle编译一下:

发现木有打印出来呀,啥情况呢?其实是因为配置目录这块有错:

更改一下:

再次运行:

ok,木啥问题,那证明目前咱们的配置一切正常,接下来要做的就是利用这个processor来自动生成这个类,再生成正式的之前我们先来做个实验生成一个简单的测试类,在保证生成一切正常之后再来生成最终我们想要的:

那怎么实现呢?其实是有工具的,需要再重写一个方法:

但是!!实际可以更加方便的工具来写,叫:javapoet,下面先来增加它的依赖:

下面则用它来生成我们想要的这个类,怎么写,直接看:

好,编译一下,看是否Test这个类生成了:

好,接下来就需要生成我们最终想要的MainActivityBinding这个类了,具体代码就不细说了,直接贴出来:

此时将咱们自己的类给删除掉:

再编译一下:

此时再运行一正常啦,此时我们已经手动将之前我们写的MainActivityBinding类已经删掉改用processor自动生成了,这其实也就是butterknife最核心的做法,可以发现用它并不怎么影响性能,因为只是编译期间会费时一点,但是打包之后其实就是我们平常自己写的那个findViewById,只是说在我们调用ButterKnife.bind(this);用反射主动调用了一下我们生成的类而已,说实话在不了解其原理之前我对于butterknife使用是表示抗拒的,但通过自己实现一遍之后发现确实是个好东东,等于就是将我们双手给解放了,本质跟我们手动写也没啥大的区别。

不过还差最后一步,目前我们生成的代码是硬编码的,如下:

这里就需要用到process传过来的两个参数啦:

第一个参数表示总注解,目前我们只使用了BindView这个注解,所以这个集合只会有一个元素。

第二个参数可能程序中有多个processor,每个processor都对应一个RoundEnvironment。

接下来我们来改造一下,将其写活:

拿类来说明它们俩:

所以咱们可以遍历EnclosedElements,来获取注解:

具体怎么写活呢,直接贴代码就不多解释了,其实也就是细节处理:

为了防止生成的类跟我们写的类重名,所以规则加了一个“$”:

所以我们的bind()方法也得增加一个“$”:

再编译:

由于变成动态生成了,所以我们可以随意更改id之类的,最后我们来试一下:

再次编译:

至此!!关于butterknife的整个原理过程完全剖析完啦,过程还是挺麻烦的,但走一遍之后有种茅塞顿开的感觉~~

手写butterknife来剖析其原理的更多相关文章

  1. 黑马vue---40、结合Node手写JSONP服务器剖析JSONP原理

    黑马vue---40.结合Node手写JSONP服务器剖析JSONP原理 一.总结 一句话总结: 服务端可以返回js代码给script标签,那么标签会执行它,并且可带json字符串作为参数,这样就成功 ...

  2. 第四章 生命周期函数--36 结合Node手写JSONP服务器剖析JSONP原理

  3. 移动架构-手写ButterKnife框架

    ButterKnife在实际开发中有着大量运用,其强大的view绑定和click事件处理,使得开发效率大大提高,同时增加了代码的阅读性又不影响其执行效率 注解的分类 注解主要有两种分类,一个是运行时, ...

  4. 手写ButterKnife

    开发中使用注解框架可以极大地提高编码效率,注解框架用到的技术可以分为两种,运行时注解跟编译时注解.运行时注解一般配合反射机制使用,编译时注解则是用来生成模板代码.这里我们分别使用这两种方法实现Butt ...

  5. 关于布隆过滤器,手写你真的知其原理吗?让我来带你手写redis布隆过滤器。

    说到布隆过滤器不得不提到,redis, redis作为现在主流的nosql数据库,备受瞩目:它的丰富的value类型,以及它的偏向计算向数据移动属性减少IO的成本问题.备受开发人员的青睐.通常我们使用 ...

  6. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  7. 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)

    上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...

  8. (二)springMvc原理和手写springMvc框架

    我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)Sp ...

  9. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

随机推荐

  1. c# 基础类型探索

    一.前言 本章节主要是探索 C# 的基本类型,一直以来我本人常用都是 int .double.bool.decimal.string 这五个类型,其对其它类型没有认真了解过.只是以前在学习的时候背了些 ...

  2. solr查询返回有中括号【可用】

    看图 解决方法: 两个core名称一样就对了 有些版本的solr就是schema.xml文件 这个方法好像不行,再找找看,先记录一下 2019-06-29 改完上面后要重启加载一下core 先看一下

  3. Python3 - 随便说一下

    Ⅰ编程语言基础知识 ⅡPython 语言概述 Ⅰ编程语言基础知识 编程语言总体分以为机器语言.汇编语言.高级语言: 机器语言:计算机硬件能够直接使用的编程语言,二进制的集合,属于低级语言. 汇编语言: ...

  4. python 之 前端开发( DOM操作)

    11.47 DOM操作 查找节点: 11.471 直接查找 document.getElementById //根据ID获取唯一一个标签 document.getElementsByClassName ...

  5. 09 IO流(六)——ByteArray字节流、流对接

    字节数组流 ByteArray流是节点流. 前面讲到的文件字节流,文件字符流,他们的源都是来自于pc硬盘. 本节要讲的字节数组流的源是来自于内存或网络. 它更合适用来处理来自网络的字节流. 由于源并不 ...

  6. vue基于 element ui 的按钮点击节流

    vue的按钮点击节流 场景: 1.在实际使用中,当我们填写表单,点击按钮提交的时候,当接口没返回之前,迅速的点击几次,就会造成多次提交. 2.获取验证码,不频繁的获取. 3.弹幕不能频繁的发 基于这几 ...

  7. Liar CodeForces - 822E (dp,后缀数组)

    大意: 给定串$s,t$, 给定整数$x$, 求判断$t$是否能划分为至多$x$段, 使这些段在$s$中按顺序,不交叉的出现. 设$dp_{i,j}$表示$s$匹配到$i$位, 划分了$j$段, 匹配 ...

  8. sql server中:isnull(列名,0) 和isnull(列名,0)<>0 的区别

    1.isnull(参数1,参数2),判断参数1是否为NULL,如果是,返回参数2,否则返回参数1. 2.isnull(列名,0),isnull()函数是用来判断列名是否为null,如果为NUll,则返 ...

  9. 我自己用C++写了个GMM(Gaussian mixture model)模型

    我自己用C++写了个GMM(Gaussian mixture model)模型 Written for an assignment 之前粗粗了解了GMM的原理,但是没有细看,现在有个Assignmen ...

  10. 【转载】IIS网站配置不带www域名直接跳转带www的域名

    很多时候为了统一网站入口,需要将不带www的主域名解析到带www的域名记录下,当客户访问不带www的域名网址的时候自动跳转到带www的域名,在IIS Web服务器中可以通过URL重写模块来实现此功能, ...