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

心理分析: ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少认识

**求职者:**应该从 注解处理器原理 与优势说起,肯定注解处理器对解放生产力的作用。然后可以引申常见的 Butterknife,Dagger2,DBFlow。这才是加分项

优势

  1. 我们平常在使用Java进行开发Android时,经常会需要写很多重复冗余的样板代码,开发中最常见的一种,就是findViewById了,如果一个界面有很多View,写起来那叫一个要死要死。于是我们注解处理器可以帮助解决冗余的代码的,
  2. 由于是在编译器进行生成的代码,并不是通过反射实现,所以性能优势是非常高的
  3. 加快开发速度,由于减少了写繁琐的代码,会对项目进度起有利的作用

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

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

注解

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就去调用之前生成的辅助类方法,完成对被注解元素的赋值操作。

自定义注解处理器

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

  1. 首先创建一个project。
  2. 创建lib_annotations, 这是一个纯java的module,不包含任何android代码,只用于存放注解。
  3. 创建lib_compiler, 这同样是一个纯java的module。该module依赖于步骤2创建的module_annotation,处理注解的代码都在这里,该moduule最终不会被打包进apk,所以你可以在这里导入任何你想要的任意大小依赖库。
  4. 创建lib_api, 对该module不做要求,可以是android library或者java library或者其他的。该module用于调用步骤3生成的辅助类方法。

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> 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();}@Overridepublic SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported();}@Overridepublic 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 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"); for (int i = 0; i < randomElements.size(); i++) { addStatement(builder, randomElements.get(i)); } return builder.build();}private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) { builder.addStatement(String.format( "target.%1$s = %2$s

调用其他app 的lib_ButterKnife执行效率为什么比其他注入框架高?它的原理是什么...相关推荐

  1. 关于TDataSet和TFDJsonDataSets在处理数据库字段的字符集和执行效率方面的比较

    关于TDataSet和TFDJsonDataSets 在处理数据库字段的字符集和执行效率方面的比较 一.TDataSet和TFDJsonDataSets内部处理字符集的转化 1.TDataSet 1. ...

  2. 如何让你的QTP脚本执行效率更高?

    在LearnQTP上看到两篇关于如何提高QTP脚本执行效率的文章: Make your QTP scripts perform better http://www.learnqtp.com/make- ...

  3. 优化javaScript代码,提高执行效率

    今天看完书,总结了一下可以如何优化 JavaScript . 1.合并js文件 为优化性能,可以把多个js文件(css文件也可以)合并成极少数大文件.跟十个5k的js文件相比,合并成一个50k的文件更 ...

  4. 掌握这35 个小细节,助你有效提升 Java 代码的执行效率!

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者:萌小Q 来源:https://www.cnblogs.com/Qian123/p/60 ...

  5. 【C 语言】数组作为参数退化为指针问题 ( 问题描述 | 从编译器角度分析该问题 | 出于提高 C 语言执行效率角度考虑 | 数组作为参数的推荐方案 )

    文章目录 一.问题描述 二.从编译器角度分析该问题 三.数组作为参数的推荐方案 一.问题描述 将 数组 作为 函数参数 , 传递时会 退化为指针 ; 数组的首地址 , 变为指针地址 , 函数中无法判定 ...

  6. php 执行效率,PHP 函数执行效率的小比较

    就是把原来的数组中的数都"拆"成"单"位的. 下面是自己写的一个函数: function splitstrtoarray_mine($array) { $new ...

  7. 浅谈代码的执行效率(3):缓存与局部性

    在前两篇文章里,我们讨论了程序性能的两个方面,一是算法(广义的算法,即解决问题的方法),二是编译器.通过这两个方面,我想表达的意思是,一段程序的执行效率,是很难从表面现象得出结论的,至少从一些简单的层 ...

  8. 浅谈代码的执行效率(2):编译器的威力

    在上一篇文章中,我主要表达了这样一个观点:影响程序效率的关键之一是算法,而算法的选择与优化,和是否多一个赋值少一个判断的关系不大.关于算法的选择,我谈到其理论上的复杂度,并不直接反映出效率.因为在实际 ...

  9. 用jamon来监控你的sql执行效率

    /** *作者:张荣华 *日期:2008-2-25 **/ 之前有一篇文章讲到如何使用jamon来监控请求以及方法得调用(原文地址见:[url]http://www.iteye.com/post/35 ...

最新文章

  1. 07.GitHub实战系列~7.Git之VS2013团队开发(如果不想了解git命令直接学这篇即可)...
  2. 使用Angular CLI从蓝本生成代码
  3. python 3解释器_Python3解释器
  4. 腾讯地图api修改信息窗口样式_DOTA2 地图编辑器指南(二):总览
  5. Spring+ Spring cloud + SSO单点登录应用认证
  6. 自动以及手动清除手机垃圾文件
  7. Objective-C学习中对 C语言的扩展
  8. [css] 请问触发hasLayout的后果是什么?
  9. 罗盘时钟编码代码_安全研究 | 利用macOS Dock实现代码的持久化执行
  10. CodeForces - 1485B(找规律+求和) acm寒假集训日记21/12/31or22/1/1
  11. [SQL实战]之获取所有员工当前的manager
  12. web之nginx相关配置二
  13. 影视剧中的歌曲怎么录制 怎么录背景音乐
  14. 饶刚:做好债券研究 完善团队建设 为投资人打造持续回报的特色固收产品
  15. 史密斯圆图串并联口诀_阻抗匹配与史密斯圆图基本原理『一』
  16. andriod安装linux系统
  17. 苹果在中国失掉 iPad 商标
  18. 线性神经网络原理以及MATLAB算法实现权值拟合和可线性分类
  19. 基于G-sensor的计步解决方案
  20. java 移动图片_Java写的一个简单的图片移动小程序

热门文章

  1. Android之getCacheDir()和getFilesDir()方法区别
  2. foreach循环符合就不往下走了_柴油发电机组冷却液循环故障解决方法
  3. 6部BBC “教材级” 地理纪录片,有生之年必看系列!
  4. 5道谷歌面试题:即使是天才也要怀疑自己能力了(附答案)
  5. 怎样借助Python爬虫给宝宝起个好名字
  6. mac apache2 php,Mac OSX 之 PHP开发环境Apache2配置
  7. mysql 卸载插件_MySQL 插件安装或卸载(window validate_password 为例)
  8. 实现图片打乱_疫情过后,是否打乱了你前进的脚步?面对现状,你将如何开展新的征程?...
  9. $.ajax datatype默认是什么类型,理解jquery ajax中的datatype属性选项值
  10. php年月日滚动选择,Unity3d—做一个年月日选择器(Scroll Rect拖动效果优化)— 无限滚动 + 锁定元素...