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

用朴素贝叶斯分类模型判断病人是否感染幽门螺杆菌(Python实现) 朴素贝叶斯,Python 阅读详情

面试官: 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<String> 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<String> getSupportedAnnotationTypes() {
    Set<String> 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<AnnotatedRandomElement>());
    }
    annotatedElementMap.get(qualifier).add(randomElement);
}

生成类文件

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

for (Map.Entry<String, List<AnnotatedRandomElement>> 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<AnnotatedRandomElement> 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",
            randomElement.getElementName().toString(),
            randomElement.getRandomValue())
    );
}

private TypeSpec createClass(String className, MethodSpec constructor) {
    return TypeSpec.classBuilder(className + "_Random")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addMethod(constructor)
            .build();
}

private String getPackage(String qualifier) {
    return qualifier.substring(0, qualifier.lastIndexOf("."));
}

private String getClassName(String qualifier) {
    return qualifier.substring(qualifier.lastIndexOf(".") + 1);
}

通过以上几行代码,创建了类文件。在类的构造函数中添加参数(target), 并为每一个被注解元素添加语句"target.%1$s = %2$s",最后通过javaFile.writeTo(filer)完成文件写入。

3. 调用生成类的方法

在lib_api中新建一个类:RandomUtil.java,添加注入方法:

public static void inject(Object object) {
    Class bindingClass = Class.forName(object.getClass().getCanonicalName() + "_Random"); 
    Constructor constructor = bindingClass.getConstructor(object.getClass());
    constructor.newInstance(object);
}

这里利用反射找到了以“Object类名_Random”命名的生成类,并调用它的构造方法。而在我们之前的注解处理器中,我们已在生成类的构造方法中实现了属性的赋值操作。

4. 使用生成类

在app module中依赖刚才创建的库:

implementation project(':lib_annotations')
implementation project(':lib_api')
annotationProcessor project(':lib_compiler')

在Activity中的使用

public class MainActivity extends AppCompatActivity {
    @RandomInt(minValue = 10, maxValue = 1000)
    int mRandomInt;

    @RandomString
    String mRandomString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RandomUtil.inject(this);

        Log.i("RandomInt ==> ", mRandomInt + "");
        Log.i("RandomString ==> ", mRandomString);
    }
}

编译,运行,查看结果:

RandomInt ==>: 700
RandomString ==>: HhRayFyTtt

被注解的元素成功被自动赋值,说明注入成功。

调试

注解处理器的debug跟普通的代码debug有点不同:

在当前工程路径下输入命令

gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac

并在Edit Configurations中新添加一个远程配置(remote),名字随意,端口为5005。 然后点击debug按钮,就可以连接上远程调试器进行Annotation的调试了。

最后帮大家整理了一些 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构学习文档,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习,有需要的小伙伴请去我 【GitHub】 进行查看。

GitHub地址:  
https://github.com/733gh/Android-T3 

31、工程中不确定性量化与优化的全面解析 本文全面解析了工程中不确定性量化与优化的关键方法。针对数据充足与不足两种情况,分别介绍了概率理论和非概率方法在不确定性建模中的应用;系统阐述了采样方法、FORM/SORM、泰勒级数近似等不确定性传播技术;深入比较了RBDO与RDO两种主流优化框架的原理、步骤及适用场景,并通过两杆桁架案例展示了具体实现过程。文章旨在为工程师提供一套完整的从不确定性分析到优化设计的方法论,提升工程系统的可靠性与稳健性。 阅读详情

相关推荐

Holtek BS45F3833芯片深度剖析:如何用高集成MCU重塑超声波雾化市场格局?

本文深度剖析Holtek BS45F3833芯片如何通过高集成MCU技术重塑超声波雾化市场。该芯片集成了PWM直驱、电容检水、触摸控制等核心功能,显著提升雾化精度和效率,同时降低BOM成本和开发难度。文章通过实测数据和实战案例,展示了其在香薰机、车载设备及工业加湿器等场景的创新应用,为开发者提供了一站式解决方案。

weixin_29192211的博客 414

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

本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注 面试官: ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么 心理分析: ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少认识 ...

Android_SE的博客 409

彩票预测的可能性与算法分析

彩票作为一种以随机数为基础的赌博形式,吸引了大量希望一夜暴富的参与者。尽管许多人希望通过某种方式预测彩票结果,以增加中奖几率,但彩票预测的可能性依然是一个充满争议和误解的话题。本文将探讨彩票是否能够预测的理论依据,分析可能的预测算法,并解释这些算法为何在实际应用中难以奏效。

m0_38012434的博客 2万+

ButterKnife的使用和原理

ButterKnife的使用和原理 参考文档: ButterKnife的使用:https://segmentfault.com/a/1190000016460847 ButterKnife的原理:https://juejin.cn/post/6844904181158019079 一、前言 ButterKnife是一个专注于Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。 使用ButterKnife对性能基

weixin_43750332的博客 1万+

网络攻防实验——网络监听

利用CAIN实现ARP监听。

Trieyes_of_feng的博客 3197

BIDS数据存储格式介绍与dcm2bids安装与使用

BIDS格式&dcm2bids安装与使用

machinelearning_er的博客 7319

android edittext_一文带你读懂Android必备之ButterKnife的基本使用及实现原理

正式开始之前插播一条硬广哈哈:字节跳动2020届校园招聘已经正式开启,涉及研发、产品、设计、市场、销售、职能/支持、运营七大类,工作地点分布北京、上海、深圳、成都、厦门、南京、杭州、武汉、广州等热门城市,要求很简单,只要你是2020年毕业的高校学生,都可以积极参与,直接到到官网:https://job.bytedance.com/campus/position 查找自己感兴趣的职位,投递简历并填写...

weixin_39931390的博客 301

信贷审批中的那些坑:从零开始搭建风控策略体系的5个关键步骤

本文深入探讨了信贷审批中的风控策略体系搭建,从数据陷阱识别、规则冲突诊断到反欺诈攻防,提供了5个关键步骤的实战指南。重点解析了如何通过特征工程、决策树优化和模型可解释性技术,构建动态平衡的风控体系,有效降低逾期风险并提升审批效率。

weixin_29253711的博客 416

ButterKnife框架原理和使用

大部分Android开发应该都知道@JakeWharton 大神的ButterKnife注解库,使用这个库我们可以不用写很多无聊的findViewById()和setOnClickListener()等代码 ButterKnife项目的主页在这里:http://jakewharton.github.io/butterknife/ 简单介绍一下使用方法:public class ExampleAc

DreamChan博客 894

前端埋点的实践

1、前言 开始正文前先介绍一下埋点的概念~,(熟悉的朋友可以略过) 前端埋点: 是一种手段,它的目的是上报相关行为数据,相关人员以数据为依据来分析产品在用户端的使用情况,根据分析出来的结果辅助产品优化、迭代。 BI:商业智能,公司内部做数据分析相关的部门。 2、背景 在流量红利逐渐消失的现在,数据的采集、分析和精细化的运营显得更加重要,所以埋点的需求在互联网产品中是很常见的,目的上面也提到了。我们的追求就是又快又好的做好埋点工作,但是现实却不那么美好;目前我们团队在前端埋点方面存在一些痛点: 在构造埋点字段

Arui_0的博客 1773

OpenEuler22.04 快速部署Docker:华为云镜像源配置指南

本文详细介绍了在OpenEuler 22.04系统上快速部署Docker的完整流程。针对系统默认源版本老旧的问题,重点讲解了如何配置华为云镜像源以加速安装,并涵盖Docker引擎、Docker Compose的安装、服务启动验证、权限配置、镜像加速等核心步骤与常见问题排查,帮助用户高效搭建稳定的容器化开发环境。

http9protocoller的博客 174

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

面试官: ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么心理分析: ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少认识**求职者:**应该从 注解处理器原理 与优势说起,肯定注解处理器对解放生产力的作...

weixin_39986896的博客 108

【Bug已解决】Claude Code Auto-update failed 自动更新失败的解决方案

方案适用场景推荐指数清理残留重新安装已经损坏,需要快速恢复可用⭐⭐⭐⭐⭐修复目录权限权限不足导致更新反复失败⭐⭐⭐⭐⭐关闭自动更新网络/安全软件环境不稳定⭐⭐⭐切换原生安装方式想从根源规避 npm 更新问题⭐⭐⭐⭐自检修复脚本团队/服务器环境需要自动化恢复⭐⭐⭐报错的本质是自动更新过程中的文件替换操作因权限不足或被中断而处于不完整状态,而不是 Claude Code 本身的功能缺陷。遇到问题先清理残留文件再重新安装,不要在损坏状态上反复尝试修复;

一键难忘的博客 1万+

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

了解了基本的知识点后,我们应该尝试去使用这些技巧。接下来是实践时间,我们来开发一个简单的例子,利用注解处理器来自动产生随机数字和随机字符串。首先创建一个project。创建lib_annotations, 这是一个纯java的module,不包含任何android代码,只用于存放注解。创建lib_compiler, 这同样是一个纯java的module。

2401_84150154的博客 827

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

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

本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注,盖亚! 面试官: ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么 心理分析: ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少认识 求职者:应该从 注解处理器原理 与优势说起,肯定注解处理器对解放生产力的作用。然后可以引申常见的 Bu.

Androidyyds的博客 249

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

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

本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注 面试官:ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么 心理分析:ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少...

初一十五的博客 276

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

面试官: ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么 心理分析: ButterKnife框架一直都是使用,很少又开发者对butterknife深入研究的,既然你是面试Android高级岗位,自然需要有相应被问到原理的准备,面试官想问你对注解处理器了解多少,Android编译流程有多少认识 **求职者:**应该从 注解处理器原理 与优势说起,肯定注解处理器对解放生产力的作用。然后可以引申常见的 Butterknife,Dagger2,DBFlow。这才是加分项 .

十年代码写十年 290

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

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

m0_66265001的博客 148
上一篇: 性能优化:Android中Bitmap内存大小优化的几种常见方式
下一篇: ASM 字节码插桩:监控大图加载
懂你的大海
博客等级 码龄6年 9049粉丝 1381原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值