⚠️创建的模块是java模块而非Android Library,如果创建的是后者,则无法使用AbstractProcessor

按照书上讲述的,想要自己去仿写一下butterknife

最终的项目结构如上图所示。其中annotations是放不同注解的java libary,process是注解处理器,也是个java libary,butterKnife是个android libary是为了实现最终的onbind()方法

最终生成的文件在这个位置(一开始找错了,以为是resource下 检查了很多次都没有 最终全局搜了一下发现可能因为as 和gradle版本的问题,最终生成的文件在这个路径下)

ButterKinfe的实质是代替我们自己去写findViewById以及OnClick,解放了我们的双手,使用BindView等注解实现对应的方法,但是实质上它的内部仍然是需要通过findViewById()的方式去寻找资源id,并将其与控件进行绑定,如下图所示

因此,我们仿写ButterKnife的实质也就是自己去定义类似的注解,并生成对应的文件和功能。

1.在项目中新建一个Java Library来专门存放注解,这个Library名为annotations,类似于下图的做法

该java libary中的build.gradle配置如下

plugins {id 'java-library'
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])
}java {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8
}

2.在项目中再新建一个Java Library来存放注解处理器,这个Library名为process(注意路径,最一开始写的时候我一不小心把部分路径丢失了,导致一直无法生成正确的结果)由于process是注解处理器,因此需要手动或自动生成Processor文件,如下图所示

我使用的是Google开源的AutoService,build.gradle配置如下

plugins {id 'java-library'
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'compileOnly 'com.google.auto.service:auto-service:1.0-rc3'implementation project(path: ':annotations')
}java {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8
}

并在处理器中添加@AutoService(Processor.class)

举一个简单的例子BindString

在没有ButterKnife之前,使用string,类似于

public class MainActivity extends AppCompatActivity {@BindView(R.id.text_view)TextView textView;//@BindString(R.string.app_name)String meg = null;@OnClick(R.id.text_view)public void onClick(View view){switch (view.getId()){case R.id.text_view:meg = getResources().getString(R.string.app_name);Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();break;}}

使用之后

public class MainActivity extends AppCompatActivity {@BindView(R.id.text_view)TextView textView;@BindString(R.string.app_name)String meg;@OnClick(R.id.text_view)public void onClick(View view){switch (view.getId()){case R.id.text_view:Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();break;}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);TextView textView = findViewById(R.id.text_view);}
}
生成的MainActivity$ViewBinder文件
package com.example.farmedemo;
import android.view.View;
public class MainActivity$ViewBinder{
public MainActivity$ViewBinder(final com.example.farmedemo.MainActivity target){
target.textView =(android.widget.TextView)target.findViewById(2131230975);
(target.findViewById(2131230975)).setOnClickListener(new View.OnClickListener() {
public void onClick(View p0) {
target.onClick(p0);
}
});
target.meg =(java.lang.String)target.getBaseContext().getResources().getString(2131623963);
}
}
AnnotationCompiler
package com.example.process;import com.example.annotations.BindString;
import com.example.annotations.BindView;
import com.example.annotations.OnClick;
import com.google.auto.service.AutoService;import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {//生成文件的对象Filer filer;@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);//初始化生成文件的对象filer = processingEnvironment.getFiler();}/*** 声明注解处理器要处理的注解* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> types = new HashSet<>();types.add(OnClick.class.getCanonicalName());types.add(BindView.class.getCanonicalName());types.add(BindString.class.getCanonicalName());return types;}/*** 声明注解处理器支持的java源版本* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {return processingEnv.getSourceVersion();}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//获取到程序中的注解 并且和它所在的类对象一一对应起来Map<TypeElement, ElementForType> andParseTargets = findAndParseTargets(roundEnvironment);//写文件if(andParseTargets.size()>0){Iterator<TypeElement> iterator = andParseTargets.keySet().iterator();Writer writer = null;while (iterator.hasNext()){TypeElement typeElement = iterator.next();ElementForType elementForType = andParseTargets.get(typeElement);String activityName = typeElement.getSimpleName().toString();//获取到新类名String newClazzName = activityName+"$ViewBinder";//获取到包名String packageName = getPackageName(typeElement);//创建java文件try {JavaFileObject sourceFile = filer.createSourceFile(packageName+ "." + newClazzName);writer = sourceFile.openWriter();StringBuffer stringBuffer = getStringBuffer(packageName, newClazzName, typeElement, elementForType);writer.write(stringBuffer.toString());} catch (IOException e) {e.printStackTrace();}finally {if(writer != null){try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}}return false;}/*** 获取到所有的注解 以及将注解和Activity一一对应起来* @param roundEnvironment*/private Map<TypeElement, ElementForType> findAndParseTargets(RoundEnvironment roundEnvironment) {Map<TypeElement, ElementForType> map = new HashMap<>();//获取到模块中所有用到了BindView的节点Set<? extends Element> viewElelments = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获取到模块中所有用到了OnClick的节点Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);//获取到模块中所有用到了BindString的节点Set<?extends Element> stringElements = roundEnvironment.getElementsAnnotatedWith(BindString.class);//遍历所有的成员变量节点  一一对应的封装到ElementForType对象中for (Element viewElelment : viewElelments) {//转换VariableElement variableElement = (VariableElement) viewElelment;//获取到它的上一个节点  成员变量节点的上一个节点  就是类节点TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//获取到类节点在map中所对应的valueElementForType elementForType = map.get(typeElement);List<VariableElement> viewElements;//如果这个类节点在map中已经存在if(elementForType !=null){//获取到类节点所对应的值中的控件节点的集合viewElements = elementForType.getViewElements();//如果集合为空就创建一个新的集合  然后放置到类节点所对应的值中if(viewElements == null){viewElements = new ArrayList<>();elementForType.setViewElements(viewElements);}}else{//如果elementForType为空  就创建一个新的elementForType = new ElementForType();//同时创建一个新的控件的节点的集合viewElements = new ArrayList<>();elementForType.setViewElements(viewElements);if(!map.containsKey(typeElement)){map.put(typeElement,elementForType);}}//最后  将遍历到的这个控件的节点的对象放置到控件的节点封装类中viewElements.add(variableElement);}//遍历所有的点击事件的方法的节点  并且封装在对象中for (Element methodElement : methodElements) {ExecutableElement executableElement = (ExecutableElement) methodElement;TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();ElementForType elementForType = map.get(typeElement);List<ExecutableElement> executableElements;logUtil(elementForType+"");if(elementForType !=null){executableElements = elementForType.getMethodElements();if(executableElements == null){executableElements = new ArrayList<>();elementForType.setMethodElements(executableElements);}}else{elementForType = new ElementForType();executableElements = new ArrayList<>();elementForType.setMethodElements(executableElements);if(!map.containsKey(typeElement)){map.put(typeElement,elementForType);}}executableElements.add(executableElement);}//遍历成员变量节点并封装for (Element viewElelment : stringElements) {//转换VariableElement variableElement = (VariableElement) viewElelment;//获取到它的上一个节点  成员变量节点的上一个节点  就是类节点TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//获取到类节点在map中所对应的valueElementForType elementForType = map.get(typeElement);List<VariableElement> viewElements;//如果这个类节点在map中已经存在if(elementForType !=null){//获取到类节点所对应的值中的控件节点的集合viewElements = elementForType.getStringElements();//如果集合为空就创建一个新的集合  然后放置到类节点所对应的值中if(viewElements == null){viewElements = new ArrayList<>();elementForType.setStringElements(viewElements);}}else{//如果elementForType为空  就创建一个新的elementForType = new ElementForType();//同时创建一个新的控件的节点的集合viewElements = new ArrayList<>();elementForType.setStringElements(viewElements);if(!map.containsKey(typeElement)){map.put(typeElement,elementForType);}}//最后  将遍历到的这个控件的节点的对象放置到控件的节点封装类中viewElements.add(variableElement);}return map;}/*** 获取包名的方法* @param typeElement*/public String getPackageName(Element typeElement){//获取包名PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(typeElement);Name qualifiedName = packageOf.getQualifiedName();return qualifiedName.toString();}public void logUtil(String message){Messager messager = processingEnv.getMessager();messager.printMessage(Diagnostic.Kind.NOTE,message);}/*** 获取到类的拼装语句的方法* @param packageName* @param newClazzName* @param typeElement* @param elementForType* @return*/public StringBuffer getStringBuffer(String packageName,String newClazzName,TypeElement typeElement,ElementForType elementForType ) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("package " + packageName + ";\n");stringBuffer.append("import android.view.View;\n");stringBuffer.append("public class " + newClazzName + "{\n");stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) {List<VariableElement> viewElements = elementForType.getViewElements();for (VariableElement viewElement : viewElements) {//获取到类型TypeMirror typeMirror = viewElement.asType();//获取到控件的名字Name simpleName = viewElement.getSimpleName();//获取到资源IDint resId = viewElement.getAnnotation(BindView.class).value();stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\n");}}if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) {List<ExecutableElement> methodElements = elementForType.getMethodElements();for (ExecutableElement methodElement : methodElements) {int[] resIds = methodElement.getAnnotation(OnClick.class).value();String methodName = methodElement.getSimpleName().toString();for (int resId : resIds) {stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() {\n");stringBuffer.append("public void onClick(View p0) {\n");stringBuffer.append("target." + methodName + "(p0);\n");stringBuffer.append("}\n});\n");}}}if (elementForType != null && elementForType.getStringElements() != null && elementForType.getStringElements().size() > 0) {List<VariableElement> variableElements = elementForType.getStringElements();for (VariableElement variableElement : variableElements) {//获取到类型TypeMirror typeMirror = variableElement.asType();//获取到控件的名字Name simpleName = variableElement.getSimpleName();//获取到资源IDint resId = variableElement.getAnnotation(BindString.class).value();stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.getBaseContext().getResources().getString(" + resId + ");\n");stringBuffer.append("}\n}\n");}
//    }return stringBuffer;}return stringBuffer;}
}
ElementForType
package com.example.process;import java.util.List;import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;public class ElementForType {//所有绑定View的成员变量的节点的集合List<VariableElement> viewElements;//所有点击时间方法的节点的集合List<ExecutableElement> methodElements;List<VariableElement> stringElements;public List<VariableElement> getStringElements() {return stringElements;}public void setStringElements(List<VariableElement> stringElements) {this.stringElements = stringElements;}public List<VariableElement> getViewElements() {return viewElements;}public void setViewElements(List<VariableElement> viewElements) {this.viewElements = viewElements;}public List<ExecutableElement> getMethodElements() {return methodElements;}public void setMethodElements(List<ExecutableElement> methodElements) {this.methodElements = methodElements;}
}

可以看出,butterKnife为我们做了很多事情,上面的代码距离真正的butterknife相差很大,butterknife一般不用于组件化开发,因为会导致控件Id的重复。

Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试相关推荐

  1. Android:dagger2让你爱不释手-基础依赖注入框架篇

    前言 dagger2的大名我想大家都已经很熟了,它是解决Android或java中依赖注入的一个类库(DI类库).当我看到一些开源的项目在使用dagger2时,我也有种匆匆欲动的感觉,因此就立马想一探 ...

  2. Android进阶三部曲 第一部《Android进阶之光》已出版(内含勘误)

    独立博客版本请点击这里 勘误帖请点击这里 源码地址:https://github.com/henrymorgen/android-advanced-light 进阶三部曲第二部:http://liuw ...

  3. Android 依赖注入框架 Dagger2使用

    前言 Dagger 2这个匕首确实很难上手,上手后又比较难瞬间掌握,可以这么说,刚开始使用就是用来尝(zhuang)鲜(X)的,但相信随着使用的加深,会不断体会到它对于整个项目架构的极强辅助作用,能使 ...

  4. 《android进阶之光》——事件总线(上)

    今日阅读:<android进阶之光>的事件总线部分,整理如下 事件总线知识点: 1.EventBus:就是事件,可以是任意对象 2.Subscriber:事件的订阅者 3.Publishe ...

  5. 【C语言进阶深度学习记录】二十六 C语言中的字符串与字符数组的详细分析

    之前有一篇文章是学习了字符和字符串的,可以与之结合学习:[C语言进阶深度学习记录]十二 C语言中的:字符和字符串 文章目录 1 字符串的概念 1.1 字符串与字符数组 1.2 字符数组与字符串代码分析 ...

  6. 【C语言进阶深度学习记录】十九 #pragma使用与分析

    文章目录 1 #pragma 概念简介 1.1 #pragma message 的用法 1.2 #pragma once 的用法 1.3 #pragma pack 的用法 1.31 struct占用的 ...

  7. 【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )

    文章目录 总结 一.Android 事件设置三要素 二.修饰注解的注解 三.Android 事件依赖注入步骤 总结 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...

  8. 【C语言进阶深度学习记录】三十八 C/C++语言中的函数声明与函数定义

    文章目录 1 函数的声明和定义 1.1 代码分析 2 总结 1 函数的声明和定义 声明的意义在于告诉编译器程序单元的存在.只是告诉编译器它存在但是不在声明这里定义,有可能在当前文件中的其他地方或者其他 ...

  9. 【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)

    学习交流加 个人qq: 1126137994 个人微信: liu1126137994 学习交流资源分享qq群: 962535112 在我之前学习底层的知识的时候,也写过相关的内容.可以对比的学习:[软 ...

最新文章

  1. JS数组方法(forEach()、every()、reduce())
  2. Programming Pearls Essay 01
  3. c语言hellowwo所占字节数,哪个懂C语言?帮忙做~个题,跪求
  4. 在Windows环境下配置QT Creator 读取NC文件(NetCDP,C++接口)
  5. [PAT B1036]跟奥巴马一起编程(15分)
  6. button 样式_缩减 SCSS 50%样式代码的 14 条实战经验
  7. 文件操作03 - 零基础入门学习C语言62
  8. Linux下java进程CPU占用率高分析方法
  9. 国密 SM4 高并发服务 加压测服务 加生成秘钥 结合上篇一起使用 国密 SM2 SM3 SM4 后续升级版本,内容丰富单独写一篇百万压测4000毫秒加解密
  10. ffplay播放摄像头
  11. win11移动磁盘打不开 Windows11打开移动磁盘的解决方法
  12. 快速生产地图瓦片解决方案:多任务切图
  13. jquery高级之妙味云课堂笔记
  14. 按颜色分类:青绿色系(Blue Green)
  15. 无旋Treap(fhq)
  16. python2(基本)
  17. 评分卡:WOE、IV、PSI计算及ROC和KS曲线
  18. (转)没有灵魂,只有交易 - 为何“苹果”会杀人
  19. spring---自定义Filter有两种方式
  20. 《UDS/OBD诊断需求编辑工具》总目录

热门文章

  1. mysql增加索引、删除索引、查看索引
  2. JAVA进阶 —— Stream流
  3. Java 编写万年历
  4. oracle字体为红色,ORACLE 显示 中文字体
  5. 桌面怎么设置 计算机 网络连接,电脑桌面的本地连接ip地址可以设置吗_本地连接ip地址设置方法 - 驱动管家...
  6. 欧债危机阴影不散,非美前景不容乐观
  7. 新建word文档默认作者如何修改
  8. L1-2 太神奇了 (5 分)
  9. 维纶屏幕配方倒入导出方法
  10. maya humanIk 动画pose快速镜像 插件及教程