ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么

  1. 我们平常在使用Java进行开发Android时,经常会需要写很多重复冗余的样板代码,开发中最常见的一种,就是findViewById了,如果一个界面有很多View,写起来那叫一个要死要死。于是我们注解处理器可以帮助解决冗余的代码的,

  2. 由于是在编译器进行生成的代码,并不是通过反射实现,所以性能优势是非常高的

  3. 加快开发速度,由于减少了写繁琐的代码,会对项目进度起有利的作用

接下来我们一起来看注解处理的原理

在android开发中,比较常用到的第三方库中,有不少用到了 注解处理器(Annotation Processor)。 比较常见的就有 ButterknifeDagger2DBFlow 等。

注解

Java中存在不少关于注解的Api, 比如@Override用于覆盖父类方法,@Deprecated表示已舍弃的类或方法属性等,android中又多了一些注解的扩展,如@NonNull, @StringRes, @IntRes等。

代码自动生成

使用代码自动生成,一是为了提高编码的效率,二是避免在运行期大量使用反射,通过在编译期利用反射生成辅助类和方法以供运行时使用。

注解处理器的处理步骤主要有以下:

  1. 在java编译器中构建

  2. 编译器开始执行未执行过的注解处理器

  3. 循环处理注解元素(Element),找到被该注解所修饰的类,方法,或者属性

  4. 生成对应的类,并写入文件

  5. 判断是否所有的注解处理器都已执行完毕,如果没有,继续下一个注解处理器的执行(回到步骤1)

Butterknife注解处理器的例子

Butterknife的注解处理器的工作方式如下:

  1. 定义一个非私有的属性变量

  2. 添加该属性变量的注解和传入id

  3. 调用Butterknife.bind(..)方法。

当你点击Android Studio的Build按钮时,Butterknife先是按照上述步骤生成了对应的辅助类和方法。在代码执行到bind(..)方法时,Butterknife就去调用之前生成的辅助类方法,完成对被注解元素的赋值操作。

自定义注解处理器

了解了基本的知识点后,我们应该尝试去使用这些技巧。 接下来是实践时间,我们来开发一个简单的例子,利用注解处理器来自动产生随机数字和随机字符串。

  • 首先创建一个project。

  • 创建lib_annotations, 这是一个纯java的module,不包含任何android代码,只用于存放注解。

  • 创建lib_compiler, 这同样是一个纯java的module。该module依赖于步骤2创建的module_annotation,处理注解的代码都在这里,该moduule最终不会被打包进apk,所以你可以在这里导入任何你想要的任意大小依赖库。

  • 创建lib_api, 对该module不做要求,可以是android library或者java library或者其他的。该module用于调用步骤3生成的辅助类方法。

image

1. 添加注解

在lib_annotations中添加两个注解:RandomString, RandomInt,分别用于生成随机数字和随机字符串:

@Retention(CLASS)

@Target(value = FIELD)

public @interface RandomString {

}

@Retention(CLASS)

@Target(value = FIELD)

public @interface RandomInt {

int minValue() default 0;

int maxValue() default 65535;

}

  • @interface 自定义注解,使用 @interface 作为类名修饰符

  • @Target 该注解所能修饰的元素类型,可选类型如下:

public enum ElementType {

TYPE, //类

FIELD, //属性

METHOD, //方法

PARAMETER, //参数

CONSTRUCTOR, //构造函数

LOCAL_VARIABLE,

ANNOTATION_TYPE,

PACKAGE,

TYPE_PARAMETER,

TYPE_USE;

private ElementType() {

}

}

  • @Retention 该注解的保留策略,有三种选项:

public enum RetentionPolicy {

SOURCE, //被编译器所忽略

CLASS, //被编译器保留至类文件,但不会保留至运行时

RUNTIME //保留至类文件,且保留至运行时,能在运行时反射该注解修饰的对象

}

2. 注解处理器

真正处理注解并生成代码的操作都在这里。 在写代码之前我们需要先导入两个重要的库,以及我们的注解模块:

compile ‘com.google.auto.service:auto-service:1.0-rc4’

compile ‘com.squareup:javapoet:1.9.0’

implementation project(‘:lib_annotations’)

新建类RandomProcessor.java:

@AutoService(Processor.class)

public class RandomProcessor extends AbstractProcessor{

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

}

@Override

public SourceVersion getSupportedSourceVersion() {

return super.getSupportedSourceVersion();

}

@Override

public Set getSupportedAnnotationTypes() {

return super.getSupportedAnnotationTypes();

}

@Override

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

return false;

}

}

  • @AutoService @AutoService(Processor.class)会告诉编译器该注解处理器的存在,并在编译时自动在META-INF/services下生成javax.annotation.processing.Processor文件,文件的内容为

com.rhythm7.lib_compiler.RandomProcessor

也就是说,你所声明的注解处理器都会在被写入这个配置文件中。 这样子,当外部程序装载这个模块的时候,就能通过该模块的jar包下的META-INF/services下找到具体的注解处理器的实现类名,并加载实例化,完成模块的注入。 注解处理器需要实现AbstractProcessor接口,并实现对应的方法

  • init() 可选 在该方法中可以获取到processingEnvironment对象,借由该对象可以获取到生成代码的文件对象, debug输出对象,以及一些相关工具类
  • getSupportedSourceVersion() 返回所支持的java版本,一般返回当前所支持的最新java版本即可
  • getSupportedAnnotationTypes() 你所需要处理的所有注解,该方法的返回值会被process()方法所接收
  • process() 必须实现 扫描所有被注解的元素,并作处理,最后生成文件。该方法的返回值为boolean类型,若返回true,则代表本次处理的注解已经都被处理,不希望下一个注解处理器继续处理,否则下一个注解处理器会继续处理。

初始化

较详细代码如下:

private static final List<Class<? extends Annotation>> RANDOM_TYPES

= Arrays.asList(RandomInt.class, RandomString.class);

private Messager messager;

private Types typesUtil;

private Elements elementsUtil;

private Filer filer;

private TypeonProcess()per.init(processingEnv);

messager = processingEnv.getMessager();

typesUtil = processingEnv.getTypeUtils();

elementsUtil = processingEnv.getElementUtils();

filer = processingEnv.getFiler();

}

@Override

public SourceVersion getSupportedSourceVersion() {

return SourceVersion.latestSupported();

}

@Override

public Set getSupportedAnnotationTypes() {

Set annotations = new LinkedHashSet<>();

for (Class<? extends Annotation> annotation : RANDOM_TYPES) {

annotations.add(annotation.getCanonicalName());

}

return annotations;

}

处理注解

process()方法中执行以下操作:

1.扫描所有注解元素,并对注解元素的类型做判断

for (Element element : roundEnv.getElementsAnnotatedWith(RandomInt.class)) {

//AnnotatedRandomInt是对被RandomInt注解的Elment的简单封装

AnnotatedRandomInt randomElement = new AnnotatedRandomInt(element);

messager.printMessage(Diagnostic.Kind.NOTE, randomElement.toString());

//判断被注解的类型是否符合要求

if (!element.asType().getKind().equals(TypeKind.INT)) {

messager.printMessage(Diagnostic.Kind.ERROR, randomElement.getSimpleClassName().toString() + “#”

  • randomElement.getElementName().toString() + " is not in valid type int");

}

//按被注解元素所在类的完整类名为key将被注解元素存储进Map中,后面会根据key生成类文件

String qualifier = randomElement.getQualifiedClassName().toString();

if (annotatedElementMap.get(qualifier) == null) {

annotatedElementMap.put(qualifier, new ArrayList());

}

annotatedElementMap.get(qualifier).add(randomElement);

}

生成类文件

将之前以注解所在类为key的map遍历,并以key值为分组生成类文件。

for (Map.Entry<String, List> entry : annotatedElementMap.entrySet()) {

MethodSpec constructor = createConstructor(entry.getValue());

TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);

JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();

javaFile.writeTo(filer);

}

生成类、构造函数、代码段以及文件都是利用到了javapoet依赖库。当然你也可以选择拼接字符串和自己用文件IO写入,但是用javapoet要更方便得多。

private MethodSpec createConstructor(List randomElements) {

AnnotatedRandomElement firstElement = randomElements.get(0);

MethodSpec.Builder builder = MethodSpec.constructorBuilder()

.addModifiers(Modifier.PUBLIC)

.addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), “target”);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

分享

首先分享一份学习大纲,内容较多,涵盖了互联网行业所有的流行以及核心技术,以截图形式分享:

(亿级流量性能调优实战+一线大厂分布式实战+架构师筑基必备技能+设计思想开源框架解读+性能直线提升架构技术+高效存储让项目性能起飞+分布式扩展到微服务架构…实在是太多了)

其次分享一些技术知识,以截图形式分享一部分:

Tomcat架构解析:

算法训练+高分宝典:

Spring Cloud+Docker微服务实战:

最后分享一波面试资料:

切莫死记硬背,小心面试官直接让你出门右拐

1000道互联网Java面试题:

Java高级架构面试知识整理:

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
思想开源框架解读+性能直线提升架构技术+高效存储让项目性能起飞+分布式扩展到微服务架构…实在是太多了)

其次分享一些技术知识,以截图形式分享一部分:

Tomcat架构解析:

[外链图片转存中…(img-D8xDXhhM-1713814077444)]

算法训练+高分宝典:

[外链图片转存中…(img-PfAemgpT-1713814077445)]

Spring Cloud+Docker微服务实战:

[外链图片转存中…(img-NVDcsjji-1713814077445)]

最后分享一波面试资料:

切莫死记硬背,小心面试官直接让你出门右拐

1000道互联网Java面试题:

[外链图片转存中…(img-RmlhkDUi-1713814077445)]

Java高级架构面试知识整理:

[外链图片转存中…(img-GYSx6gOc-1713814077445)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值