手写ButterKnife
一、原理简介
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相关推荐
- java bindview_手写 ButterKnife BindView
先建三个module,分别为Butterknife ButterKnife-Annotions ButterKnife-compiler,其中butterknife为Android Module其余的 ...
- 红橙Darren视频笔记 手写ButterKnife(Android Studio4.2.2 gradle-6.7.1 )
ButterKnife的github地址 https://github.com/JakeWharton/butterknife 1.ButterKnife的使用 第一步 在moudle的gradle配 ...
- Android 自定义注解详细用法,手写Butterknife黄油刀
前言 本篇文章主要讲解 Java 注解在Android中的常见用法 Java 注解(Annotation) Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释 ...
- 如果有的明星不会使用计算机,盘点娱乐圈不会用电脑手机的10大原始明星:最后一位出书都是手写...
原标题:盘点娱乐圈不会用电脑手机的10大原始明星:最后一位出书都是手写 刘雪华作为演艺界的老戏骨,演技是得到大家的认可的,可是她却不怎么懂得用电脑,所以她在自己的博客上发表声明,说自己不会用电脑,博客 ...
- 基于python的手写数字识别knn_KNN分类算法实现手写数字识别
需求: 利用一个手写数字"先验数据"集,使用knn算法来实现对手写数字的自动识别: 先验数据(训练数据)集: ♦数据维度比较大,样本数比较多. ♦ 数据集包括数字0-9的手写体. ...
- Python,OpenCV基于支持向量机SVM的手写数字OCR
Python,OpenCV基于支持向量机SVM的手写数字OCR 1. 效果图 2. SVM及原理 2. 源码 2.1 SVM的手写数字OCR 2.2 非线性SVM 参考 上一节介绍了基于KNN的手写数 ...
- Python,OpenCV使用KNN来构建手写数字及字母识别OCR
Python,OpenCV使用KNN来构建手写数字及字母识别OCR 1. 原理 1.1 手写数字识别 1.2 字母识别 2. 源码 2.1 手写数字OCR 2.2 字母OCR 参考 这篇博客将介绍如何 ...
- 深蓝学院第三章:基于卷积神经网络(CNN)的手写数字识别实践
参看之前篇章的用全连接神经网络去做手写识别:https://blog.csdn.net/m0_37957160/article/details/114105389?spm=1001.2014.3001 ...
- 深蓝学院第二章:基于全连接神经网络(FCNN)的手写数字识别
如何用全连接神经网络去做手写识别??? 使用的是jupyter notebook这个插件进行代码演示.(首先先装一个Anaconda的虚拟环境,然后自己构建一个自己的虚拟环境,然后在虚拟环境中安装ju ...
- 深度学习--TensorFlow(项目)识别自己的手写数字(基于CNN卷积神经网络)
目录 基础理论 一.训练CNN卷积神经网络 1.载入数据 2.改变数据维度 3.归一化 4.独热编码 5.搭建CNN卷积神经网络 5-1.第一层:第一个卷积层 5-2.第二层:第二个卷积层 5-3.扁 ...
最新文章
- oracle杀死进程时权限不足_当集群和数据库软件目录都被chown -R时,如何快速修复...
- 对于linux下指令的进一步扩充与巩固
- python——input() 的用法及扩展
- java合并sheet行_java poi Excel循环合并行
- SpringBoot2.0之六 多环境配置
- elk 安装与所遇问题
- Tableau可视化学习笔记:day01-02
- CentOS7 安装管理KVM虚拟机
- 计算机软件著作权许可协议范本,软件著作权使用许可合同协议范本模板.doc
- 【叁】量化思维--复利
- python 求向量间内积 和外积
- 蓝牙inquiry流程之命令下发
- 闪电贷攻击的深层原因:价格预言机操纵攻击
- 【转】如何用Word编辑参考文献
- 本周最新文献速递20220410
- Halcon中关于角度计算的算子详解
- PageAdmin CMS建站系统可视化区块的使用教程
- 微信小程序 todolist
- 论文中常用的英语短语
- python-web-下载所有xkcd漫画