一、原理简介
ButterKnife框架原理的是采用APT编译时技术,主要运用到注解+注解处理器的方式动态地为添加了BindView等注解的成员或方法生成类文件,开发者无需自己手写findViewById等等重复的代码,简化了开发者的工作量。

二、手写ButterKnife
想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。

2.1准备工作
(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。
注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲
了解了ButterKnife的原理后,今天我就带领大家手写一个简易的ButterKnife。因为ButterKnife使用到了编译时注解+反射,所以在手写项目之前,小伙伴们需要先熟悉下注解(Annotation)、反射和AbstractProcessor相关的知识。

话不多说,我们新建一个项目,命名为ButterknifeViewBind。这里先梳理下我们的逻辑,首先我们需要新建一个annotation库,专门用来存放我们的注解代码,其次还需要创建一个compiler库,用来放置编译的相关逻辑,最后我们还需要新建一个util工具库,提供注册的入口,供我们的业务使用。整个项目的目录结构如下:
工程结构
首先看下butterknife-annotations库,很简单,我们只声明了BindView这个注解类,相关代码如下:

package com.demo.butterknife_annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
好了,注解有了,现在我们需要做的就是针对我们特定的注解进行处理,这个时候我们的ButterKnifeProcessor就登场了:

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

private static final ClassName UI_THREAD =ClassName.get("androidx.annotation", "UiThread");
Map<TypeElement, List<FieldBinding>> map = new HashMap<>();private Types mTypeUtils;
private Elements mElementUtils;
private Filer mFiler;
private Messager mMessager;@Override
public synchronized void init(ProcessingEnvironment env) {super.init(env);//初始化我们需要的基础工具mTypeUtils = env.getTypeUtils();mElementUtils = env.getElementUtils();mFiler = env.getFiler();mMessager = env.getMessager();
}@Override
public SourceVersion getSupportedSourceVersion() {//支持的Java版本return SourceVersion.latestSupported();
}@Override
public Set<String> getSupportedAnnotationTypes() {//支持的注解Set<String> annotations = new LinkedHashSet<>();annotations.add(BindView.class.getCanonicalName());return annotations;
}@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//解析特定注解,生成Java文件for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {if (annotatedElement.getKind() != ElementKind.FIELD) {error(annotatedElement, "Only field can be annotated with @%s",BindView.class.getSimpleName());return true;}analysisAnnotated(annotatedElement);}for (Map.Entry<TypeElement, List<FieldBinding>> item :map.entrySet()) {TypeElement enclosingElement = item.getKey();String packageName = ((PackageElement) enclosingElement.getEnclosingElement()).getQualifiedName().toString();String className = enclosingElement.getSimpleName().toString();ClassName originClassName = ClassName.get(packageName, className);ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");//System.out.println("packageName -------------------" + packageName);//System.out.println("className -------------------" + className);//System.out.println("originClassName -------------------" + originClassName.toString());//System.out.println("bindingClassName -------------------" + bindingClassName.toString());//构造方法中拼接代码CodeBlock.Builder codeBuilder = CodeBlock.builder();for (FieldBinding fieldBinding : item.getValue()) {codeBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldBinding.getFieldName(),fieldBinding.getType(), fieldBinding.getFieldId());}//创建构造方法MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addAnnotation(UI_THREAD).addParameter(originClassName, "target").addCode(codeBuilder.build());//创建类TypeSpec typeSpec = TypeSpec.classBuilder(bindingClassName).addModifiers(Modifier.PUBLIC).addMethod(constructor.build()).build();try {//生成java文件JavaFile.builder(packageName, typeSpec).build().writeTo(mFiler);} catch (IOException e) {e.printStackTrace();}}return false;
}private void error(Element e, String msg, Object... args) {mMessager.printMessage(Diagnostic.Kind.ERROR,String.format(msg, args),e);
}private void analysisAnnotated(Element element) {TypeElement activityElement = (TypeElement) element.getEnclosingElement();List<FieldBinding> activityBinding = map.get(activityElement);if (activityBinding == null) {activityBinding = new ArrayList<>();map.put(activityElement, activityBinding);}// Verify that the target type extends from View.TypeMirror elementType = element.asType();TypeName type = TypeName.get(elementType);int fieldId = element.getAnnotation(BindView.class).value();String fieldName = element.getSimpleName().toString();FieldBinding fieldBinding = new FieldBinding(fieldId, fieldName, type);activityBinding.add(fieldBinding);
}

}
可以看到ButterKnifeProcessor使用了@AutoService注解,这个是用来做什么的呢?AutoService是Google开发的注解处理器,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,简化了我们创建注解处理器的流程。

AutoService的使用方式很简单,第一步,在我们butterknife_compiler库的buid.gradle文件中添加相关依赖:

implementation ‘com.google.auto.service:auto-service:1.0-rc3’
annotationProcessor ‘com.google.auto.service:auto-service:1.0-rc3’
接着在我们创建的注解处理器上添加@AutoService(Processor.class)注解就OK了,有没有很简单哈哈。

我们接着看下ButterKnifeProcessor的process方法,该方法在项目中的作用就是解析@ BindView注解,生成“ _ViewBinding.java”文件。在生成“ _ViewBinding.java”文件的过程中,使用到了JavaPoet开源库,what? 这又是什么?其实JavaPoet 就是用来辅助我们生成 Java 代码的一个 Java Library。同样我们需要在buid.gradle文件中添加依赖:

implementation ‘com.squareup:javapoet:1.8.0’
对JavaPoet 的API操作不太了解的童鞋可以查下相关博客,笔者这里也是一边查博客一边写的,一把辛酸泪啊~

好了到现在为止ButterKnifeProcessor也被我们搞定了,接下来就只剩下util工具库,提供注册的入口,这个就easy多了,只涉及到反射操作,我们首先新建butterknifeinject library,接着创建ButterknifeInject类,该类只有一个bind方法,代码如下:

package com.demo.butterknifeinject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public final class ButterknifeInject {

public static void bind(Object target) {try {String clsName = target.getClass().getName();Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");try {Constructor constructor = bindingClass.getConstructor(target.getClass());try {System.out.println("constructor-----------newInstance- before");constructor.newInstance(target);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}} catch (NoSuchMethodException e) {e.printStackTrace();}} catch (ClassNotFoundException e) {e.printStackTrace();}
}

}
大功告成,最后我们测试一下。在app的build.gradle文件中添加我们的library依赖:

annotationProcessor project(‘:butterknife-compiler’)
implementation project(‘:butterknife-annotations’)
implementation project(‘:butterknifeInject’)
ok,在MainActivity中我们现在就可以这么写啦:

public class MainActivity extends AppCompatActivity {

@BindView(R.id.tv_test)
TextView mTvTest;@BindView(R.id.btn_test)
Button mBtnTest;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterknifeInject.bind(this);initData();
}private void initData(){mBtnTest.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mTvTest.setText("哈哈哈哈哈嗝");}});
}

}
最后我们run一把,一切正常,不出意外的话我们的“ _ViewBinding.java”文件已经生成了,我们看下:
1577184824451.png

手写ButterKnife相关推荐

  1. java bindview_手写 ButterKnife BindView

    先建三个module,分别为Butterknife ButterKnife-Annotions ButterKnife-compiler,其中butterknife为Android Module其余的 ...

  2. 红橙Darren视频笔记 手写ButterKnife(Android Studio4.2.2 gradle-6.7.1 )

    ButterKnife的github地址 https://github.com/JakeWharton/butterknife 1.ButterKnife的使用 第一步 在moudle的gradle配 ...

  3. Android 自定义注解详细用法,手写Butterknife黄油刀

    前言 本篇文章主要讲解 Java 注解在Android中的常见用法 Java 注解(Annotation) Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释 ...

  4. 如果有的明星不会使用计算机,盘点娱乐圈不会用电脑手机的10大原始明星:最后一位出书都是手写...

    原标题:盘点娱乐圈不会用电脑手机的10大原始明星:最后一位出书都是手写 刘雪华作为演艺界的老戏骨,演技是得到大家的认可的,可是她却不怎么懂得用电脑,所以她在自己的博客上发表声明,说自己不会用电脑,博客 ...

  5. 基于python的手写数字识别knn_KNN分类算法实现手写数字识别

    需求: 利用一个手写数字"先验数据"集,使用knn算法来实现对手写数字的自动识别: 先验数据(训练数据)集: ♦数据维度比较大,样本数比较多. ♦ 数据集包括数字0-9的手写体. ...

  6. Python,OpenCV基于支持向量机SVM的手写数字OCR

    Python,OpenCV基于支持向量机SVM的手写数字OCR 1. 效果图 2. SVM及原理 2. 源码 2.1 SVM的手写数字OCR 2.2 非线性SVM 参考 上一节介绍了基于KNN的手写数 ...

  7. Python,OpenCV使用KNN来构建手写数字及字母识别OCR

    Python,OpenCV使用KNN来构建手写数字及字母识别OCR 1. 原理 1.1 手写数字识别 1.2 字母识别 2. 源码 2.1 手写数字OCR 2.2 字母OCR 参考 这篇博客将介绍如何 ...

  8. 深蓝学院第三章:基于卷积神经网络(CNN)的手写数字识别实践

    参看之前篇章的用全连接神经网络去做手写识别:https://blog.csdn.net/m0_37957160/article/details/114105389?spm=1001.2014.3001 ...

  9. 深蓝学院第二章:基于全连接神经网络(FCNN)的手写数字识别

    如何用全连接神经网络去做手写识别??? 使用的是jupyter notebook这个插件进行代码演示.(首先先装一个Anaconda的虚拟环境,然后自己构建一个自己的虚拟环境,然后在虚拟环境中安装ju ...

  10. 深度学习--TensorFlow(项目)识别自己的手写数字(基于CNN卷积神经网络)

    目录 基础理论 一.训练CNN卷积神经网络 1.载入数据 2.改变数据维度 3.归一化 4.独热编码 5.搭建CNN卷积神经网络 5-1.第一层:第一个卷积层 5-2.第二层:第二个卷积层 5-3.扁 ...

最新文章

  1. oracle杀死进程时权限不足_当集群和数据库软件目录都被chown -R时,如何快速修复...
  2. 对于linux下指令的进一步扩充与巩固
  3. python——input() 的用法及扩展
  4. java合并sheet行_java poi Excel循环合并行
  5. SpringBoot2.0之六 多环境配置
  6. elk 安装与所遇问题
  7. Tableau可视化学习笔记:day01-02
  8. CentOS7 安装管理KVM虚拟机
  9. 计算机软件著作权许可协议范本,软件著作权使用许可合同协议范本模板.doc
  10. 【叁】量化思维--复利
  11. python 求向量间内积 和外积
  12. 蓝牙inquiry流程之命令下发
  13. 闪电贷攻击的深层原因:价格预言机操纵攻击
  14. 【转】如何用Word编辑参考文献
  15. 本周最新文献速递20220410
  16. Halcon中关于角度计算的算子详解
  17. PageAdmin CMS建站系统可视化区块的使用教程
  18. 微信小程序 todolist
  19. 论文中常用的英语短语
  20. python-web-下载所有xkcd漫画

热门文章

  1. 【STM32CubeMX+Keil+PROTEUS】之---4*4键盘仿真驱动
  2. 爬虫问题,内容应该如何解码,base64解码技巧,从哪里开始分析
  3. 使用Enum.Prase及Enum.TryPrase时的注意事项
  4. 手风琴控件android,ExpandableListView实现手风琴效果
  5. 一个西瓜 4刀最多多少块
  6. Java常用API——学习笔记(7)
  7. 基于费马原理推导斯涅耳定律和广义斯涅耳定律
  8. GPG入门指南(加密/签名)
  9. 关于iPhone边缘触摸延迟现象
  10. 苹果笔记本 麦克风设置