ButterKnife在实际开发中有着大量运用,其强大的view绑定和click事件处理,使得开发效率大大提高,同时增加了代码的阅读性又不影响其执行效率

注解的分类

注解主要有两种分类,一个是运行时,一个是编译时

运行时注解:由于会影响性能,不是很推荐使用

编译时注解:编译时注解的核心原理依赖APT(Annotation Processing Tools)实现

编译时注解说明

使用了编译时注解的第三方框架主要有

  • ButterKnife:这个库是针对View,资源ID,部分事件等进行注解的开源库,它能够去除掉一些不怎么雅观的样板式代码,使得我们的代码更加简洁,易于维护,同事给予APT也能使得它的效率得到保证。ButterKnife是针对View等进行注解的开源库
  • Dragger
  • Retrofit

核心原理:APT(Annotation Processing Tools)实现

编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时javac编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是ButterKnife Dragger等开源库的基本原理

APT工具

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件

Java源文件编译成Class文件

工具是javac工具,注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以为特定的注解,注册你自己的注解处理器

怎样注册注解处理器

MyProcessor到javac中。你必须提供一个.jar文件。就像其他.jar文件一样,你打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件javax.annotation.processing.ProcessorMETA-INF/services路径下

依赖准备

需要一个注入模块,也就是一个依赖库,这是一个Android Library

新建Android依赖库

项目右键 -> New -> Module -> Android Library

需要一个Java Library,用来包含注解,注解库

新建Java依赖库

项目右键 -> New -> Module -> Java Library

同时就需要一个注解处理器,这也是一个Java Library,编译库

新建Java依赖库

项目右键 -> New -> Module -> Java Library

至此,主项目,Android依赖库,Java依赖库新建完成

添加依赖

工程gradle增加maven库和apt插件

  1. buildscript {
  2. repositories {
  3. google()
  4. jcenter()
  5. //maven库
  6. mavenCentral()
  7. }
  8. dependencies {
  9. classpath 'com.android.tools.build:gradle:3.1.3'
  10. //apt插件,如果报错则不使用apt,不添加这行
  11. //classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  12. }
  13. }
  14. allprojects {
  15. repositories {
  16. google()
  17. jcenter()
  18. //maven库
  19. mavenCentral()
  20. }
  21. }
  22. task clean(type: Delete) {
  23. delete rootProject.buildDir
  24. }

主工程gradle下引入apt

  1. apply plugin: 'com.android.application'
  2. //引入apt,如果报错则不使用apt,不添加这行
  3. //apply plugin: 'com.neenbedankt.android-apt'
  4. android {
  5. compileSdkVersion 25
  6. ···

配置主项目,Android依赖和Java依赖之间的关系

项目右键 -> Open Module Settings -> ···



如果配置完成报错,那么就不添加apt,而使用annotationProcessor

  1. android-apt plugin is incompatible with the Android Gradle plugin. Please use 'annotationProcessor' configuration instead.

此时再主项目gradle中会生成

  1. dependencies {
  2. ···
  3. implementation project(':inject')
  4. implementation project(':inject-complier')
  5. }

将其修改为,aptannotationProcessor两种形式

  1. dependencies {
  2. ···
  3. implementation project(':inject')
  4. //apt project(':inject-complier')
  5. annotationProcessor project(':inject-complier')
  6. }

否则编译器不知道由谁去负责apt

添加inject-complier的依赖inject-annotation



同样的做法,inject添加inject-annotation依赖

inject-complie添加生成源代码的com.google.auto:auto-common``com.google.auto.service:auto-service库和生成Java源文件的com.squareup:javapoet

配置完成gradle如下

  1. dependencies {
  2. implementation fileTree(include: ['*.jar'], dir: 'libs')
  3. implementation project(':inject-annotation')
  4. implementation 'com.google.auto:auto-common:0.10'
  5. implementation 'com.google.auto.service:auto-service:1.0-rc4'
  6. implementation 'com.squareup:javapoet:1.11.1'
  7. }

最终生成的各个gradle依赖如下

主项目gradle

  1. dependencies {
  2. ···
  3. implementation project(':inject')
  4. //apt project(':inject-complier')
  5. annotationProcessor project(':inject-complier')
  6. implementation project(':inject-annotation')
  7. }

Android Library

  1. dependencies {
  2. ···
  3. implementation project(':inject-annotation')
  4. }

注解库依赖没有添加任何依赖

编译库依赖

  1. dependencies {
  2. implementation fileTree(include: ['*.jar'], dir: 'libs')
  3. implementation project(':inject-annotation')
  4. implementation 'com.google.auto:auto-common:0.10'
  5. implementation 'com.google.auto.service:auto-service:1.0-rc4'
  6. implementation 'com.squareup:javapoet:1.11.1'
  7. }

代码实现

主项目代码

  1. public class MainActivity extends AppCompatActivity {
  2. @BindView(R.id.text_view)
  3. TextView textView;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. InjectView.bind(this);
  9. Toast.makeText(this, "textView=" + textView, Toast.LENGTH_SHORT).show();
  10. textView.setText("改写成功");
  11. }
  12. }

Android依赖库代码

  1. public interface ViewBinder<T> {
  2. void bind(T tartget);
  3. }
  1. public class InjectView {
  2. //绑定Activity
  3. public static void bind(Activity activity) {
  4. //通过反射拿到编译后生成的绑定内部类
  5. String className = activity.getClass().getName();
  6. try {
  7. Class<?> viewBindClass = Class.forName(className + "$$ViewBinder");
  8. ViewBinder viewBinder = (ViewBinder) viewBindClass.newInstance();
  9. viewBinder.bind(activity);
  10. } catch (ClassNotFoundException e) {
  11. e.printStackTrace();
  12. } catch (IllegalAccessException e) {
  13. e.printStackTrace();
  14. } catch (InstantiationException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }

注解库代码

  1. @Retention(RetentionPolicy.CLASS)
  2. @Target(ElementType.FIELD)
  3. public @interface BindView {
  4. int value();
  5. }

编译库代码

  1. public class FieldViewBinding {
  2. private String name;
  3. private TypeMirror typeMirror;
  4. private int resId;
  5. public FieldViewBinding(String name, TypeMirror typeMirror, int resId) {
  6. this.name = name;
  7. this.typeMirror = typeMirror;
  8. this.resId = resId;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. public TypeMirror getTypeMirror() {
  14. return typeMirror;
  15. }
  16. public int getResId() {
  17. return resId;
  18. }
  19. }
  1. @AutoService(Processor.class)
  2. public class BindViewProcessor extends AbstractProcessor {
  3. private Elements elementUtils;
  4. private Types typeUtils;
  5. private Filer filer;
  6. @Override
  7. public synchronized void init(ProcessingEnvironment processingEnvironment) {
  8. super.init(processingEnvironment);
  9. elementUtils = processingEnvironment.getElementUtils();
  10. typeUtils = processingEnvironment.getTypeUtils();
  11. filer = processingEnvironment.getFiler();
  12. }
  13. @Override
  14. public Set<String> getSupportedAnnotationTypes() {
  15. Set<String> stringSet = new LinkedHashSet<>();
  16. stringSet.add(BindView.class.getCanonicalName());
  17. return stringSet;
  18. }
  19. @Override
  20. public SourceVersion getSupportedSourceVersion() {
  21. return SourceVersion.latestSupported();
  22. }
  23. @Override
  24. public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
  25. Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
  26. for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
  27. TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
  28. List<FieldViewBinding> list = targetMap.get(enclosingElement);
  29. if (list == null) {
  30. list = new ArrayList<>();
  31. targetMap.put(enclosingElement, list);
  32. }
  33. String packageName = getPackageName(enclosingElement);
  34. int id = element.getAnnotation(BindView.class).value();
  35. String fieldName = element.getSimpleName().toString();
  36. TypeMirror typeMirror = element.asType();
  37. FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id);
  38. list.add(fieldViewBinding);
  39. }
  40. for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) {
  41. List<FieldViewBinding> list = item.getValue();
  42. if (list == null || list.size() == 0) {
  43. continue;
  44. }
  45. TypeElement enclosingElement = item.getKey();
  46. String packageName = getPackageName(enclosingElement);
  47. String complite = getClassName(enclosingElement, packageName);
  48. ClassName className = ClassName.bestGuess(complite);
  49. ClassName viewBinder = ClassName.get("com.cj5785.inject", "ViewBinder");
  50. TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder")
  51. .addModifiers(Modifier.PUBLIC)
  52. .addTypeVariable(TypeVariableName.get("T", className))
  53. .addSuperinterface(ParameterizedTypeName.get(viewBinder, className));
  54. MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
  55. .addModifiers(Modifier.PUBLIC)
  56. .returns(TypeName.VOID)
  57. .addAnnotation(Override.class)
  58. .addParameter(className, "target", Modifier.FINAL);
  59. for (int i = 0; i < list.size(); i++) {
  60. FieldViewBinding fieldViewBinding = list.get(i);
  61. String pacckageNameString = fieldViewBinding.getTypeMirror().toString();
  62. ClassName viewClass = ClassName.bestGuess(pacckageNameString);
  63. methodBuilder.addStatement("target.$L=($T)target.findViewById($L)",
  64. fieldViewBinding.getName(), viewClass, fieldViewBinding.getResId());
  65. }
  66. result.addMethod(methodBuilder.build());
  67. try {
  68. JavaFile.builder(packageName, result.build())
  69. .addFileComment("auto create make")
  70. .build().writeTo(filer);
  71. } catch (IOException e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. return false;
  76. }
  77. private String getClassName(TypeElement enclosingElement, String packageName) {
  78. int packageLength = packageName.length() + 1;
  79. return enclosingElement.getQualifiedName().toString()
  80. .substring(packageLength).replace(".", "$");
  81. }
  82. private String getPackageName(TypeElement enclosingElement) {
  83. return elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
  84. }
  85. }

由于编译库内不能存在中文,故将代码注释单独解释

  • @AutoService

    这是一个其他注解处理器中引入的注解。AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。我们可以在注解处理器中使用注解。非常方便
  • Elements

    一个用来处理Element的工具类:在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件

    PackageElement:包元素,可以获取包名

    TypeElement:类型元素,某个字段属于某种类型

    ExcutableElement:可执行元素

    VariabeElement:变量元素

    TypeParameterElement:类型参数元素
  • Types

    一个用来处理TypeMirror的工具类
  • Filer

    使用Filer你可以创建Java文件
  • process(Set<? extends TypeElement> annotations, RoundEnvironment env)

    相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
  • getSupportedAnnotationTypes

    这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上
  • getSupportedSourceVersion

    用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6

移动架构-手写ButterKnife框架的更多相关文章

  1. Android开发之手把手教你写ButterKnife框架(三)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...

  2. Android开发之手把手教你写ButterKnife框架(二)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...

  3. 手写 jQuery 框架

    1.测试页面; <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...

  4. 手写DAO框架(三)-数据库连接

    -------前篇:手写DAO框架(二)-开发前的最后准备--------- 前言 上一篇主要是温习了一下基础知识,然后将整个项目按照模块进行了划分.因为是个人项目,一个人开发,本人采用了自底向上的开 ...

  5. 手写DAO框架(二)-开发前的最后准备

    -------前篇:手写DAO框架(一)-从“1”开始 --------- 前言:前篇主要介绍了写此框架的动机,把主要功能点大致介绍了一下.此篇文章主要介绍开发前最后的一些准备.主要包括一些基础知识点 ...

  6. 手写DAO框架(一)-从“1”开始

    背景: 很久(4年)之前写了一个DAO框架-zxdata(https://github.com/shuimutong/zxdata),这是我写的第一个框架.因为没有使用文档,我现在如果要用的话,得从头 ...

  7. 手写DAO框架(四)-SQL执行

    -------前篇:手写DAO框架(三)-数据库连接--------- 前言 通过上一篇写的方法,可以灵活的获取.释放数据库连接,拿到连接之后,我们就可以执行sql了!所以,本篇介绍的就是SQL执行器 ...

  8. (二)springMvc原理和手写springMvc框架

    我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)Sp ...

  9. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

随机推荐

  1. 使用jQuery快速高效制作网页交互特效-----JavaScript操作DOM对象

    一.DOM操作分类 使用JavaScript操作DOM时通常分为三类:DOM    Core.HTMl--DOM和CSS-DOM 二.访问节点 节点属性 三.节点信息 四.操作节点的属性 语法: ge ...

  2. CSS绝对定位详解

    设置为绝对定位的元素框从文档流完全删除,并相对于其包含块定位,包含块可能是文档中的另一个元素或者是初始包含块.直线电机生产厂家 元素原先在正常文档流中所占的空间会关闭,就好像该元素原来不存在一样.元素 ...

  3. Oracle 绑定变量窥视

    绑定变量窥视功能是数据库的一个特性,自ORACLE9i版本开始引入,默认是开启的. “绑定变量窥视”表示,查询优化器在第一次调用游标时,会观察用户定义的绑定变量的值,允许优化器来确认过滤条件的选择性, ...

  4. 搭建自己的博客(八):使用fontawesome框架来添加图标以及美化详情页

    在网页中有时候会使用到图标,自己弄图标有些麻烦所以就使用了fontawesome框架. 官网:   下载地址 我使用的fontawesome版本是5.5.0版本 1.先上变化的部分

  5. CSPS2019游(tuifei)记

    %%%脸哥没脸%%% Day0,日常考前紧张,做不下题去.听各大主任送祝福(从里红(wa)到外) 然后就出发了,大巴上和云力一起坐,吃了好多东西.中午因不满火车站的不合理收费,选择了面包+火腿 下午在 ...

  6. 好久木来了,一直忙于项目(加懒惰),今天讲讲vuecli3.0的使用

    vue更新换代很快,马上vue都要出3.0了,这是一个巨大的变革,不过今天讲的是vuecli3.0,里面使用的vue仍然是2的,所有可以放心大胆使用. Vue CLI 是一个基于 Vue.js 进行快 ...

  7. Linux - /bin/sh^M: bad interpreter: No such file or directory

    问题 在Windows环境下用Notepad++写了个shell脚本,上传到Linux平台后运行报错如下: /bin/sh^M: bad interpreter: No such file or di ...

  8. vue pc element-ui class

    按需引入element-ui npm install babel-plugin-component -D   先安装这个 然后在babelrc中配置: 在plugins中加入红色框的那一部分 [ &q ...

  9. Tkinter 之Scale滑块标签

    一.参数说明 语法 作用 Scale(window, label="滑块") 滑块标题 Scale(window, label="滑块", from_=0) 滑 ...

  10. ros资料记录,详细阅读

    ROS源码分析--子话题-catkin:https://blog.csdn.net/sukha/article/details/52460492 ROS源码分析:https://blog.csdn.n ...