前言

首先需要一些先验知识:

浅谈Java/Android下的注解

Java、Android基础之—反射

Java、Android静态代理与动态代理

简介

在我们常用的框架中注解和自动生成代码的身影很常见,因为注解和自动生成的配合,从而简化和统一代码,使框架使用简单且容易扩展,典型且最熟悉的就是ButterKnife,主要功能利用注解省略了findViewById的过程,当然也提供了其他的监听、绑定等很多强大的操作符,熟悉ButterKnife源码的应该知道,ButterKnife的实现就是利用我们今天的要讲的内容,我们在文章的最后也会尝试编写一个简单的ButterKnife;

构建

1、创建Java Library

在Android项目中创建Java Library

2、配置build.gradle 文件

implementation 'com.squareup:javapoet:1.11.1'
implementation 'com.google.auto.service:auto-service:1.0-rc4'

3、创建AbstractProcessor的子类

代码的自动生成使用的是AbstractProcessor类,所以第一步要添加AbstractProcessor的实现类:

@AutoService(Processor.class)
public class MYProcessor extends AbstractProcessor {private Filer mFiler;private Elements mElements;private Messager mMessage;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mFiler = processingEnv.getFiler();mElements = processingEnv.getElementUtils();mMessage = processingEnv.getMessager();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {MethodSpec methodSpec = MethodSpec.methodBuilder("processMethod")   // 添加方法.addModifiers(Modifier.PUBLIC) // 添加方法修饰符.addParameter(String.class, "name") // 添加方法参数.build();TypeSpec typeSpec = TypeSpec.classBuilder("ProcessCreate")  // 设置要生成的类.addModifiers(Modifier.PUBLIC) // 添加类修饰符.addMethod(methodSpec)  // 将方法添加类中.build();JavaFile javaFile = JavaFile.builder("com.example.administrator.permission", typeSpec).build();   //设置JavaFiletry {javaFile.writeTo(mFiler);  // 写入} catch (IOException e) {e.printStackTrace();}return true;}@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> types = new LinkedHashSet<>();types.add(Override.class.getCanonicalName()); return types;}
}

上面是实现类的全部代码,作几点说明:

  • 添加@AutoService(Processor.class)注解

  • 主要方法介绍:

  • init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer;

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

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

  • getSupportedSourceVersion():用来指定你使用的Java版本

  • TypeSpec:设置生成类的配置

  • MethodSpec:设置方法的配置

4、 执行Make Project

执行Make Project或Build程序就会自动生成代码,代码位于build/generated/source/apt/debug/package/…

查看类中的代码,与设置的函数、参数、修饰符一致;

5、 使用生成的类

创建的代码在Build过程中创建,所以在代码运行时可以像正常类一样使用

val p = ProcessCreate()
p.processMethod("Name")

手动实现一个BindView注解框架

看看项目框架

项目中要使用Java Library放置注解和代码生成部分,所以将每个功能分开创建Library,每个Library智能如下:

  • animal:Java Library 声明注解

  • apiLibrary:Android Library 提供必须的API支持

  • app:Module 项目的主体部分

  • lib:Java Library 根据注解自动生成代码部分

1、animal

首先是要根据注解完成代码得初始化,所以第一步声明使用的注解@BindView,在View中声明int的属性值,此处的value就是资源的id。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {int value();
}

2、apiLibrary

提供Api支持,因为框架的功能是绑定并获取View控件,所以声明两个功能接口如下:

// 根据ID获取控件
public interface ViewFinder {View findView(Object o,int resId);
}
//绑定Activity
public interface ViewBinder<T> {void bindView(T host, Object o, ViewFinder viewFinder);void unBindView(T host);
}

ViewFinder和ViewBind的实现,先看看简单的一个ViewFinder,根据ID返回控件实例,就是一个FindViewbyId的过程(findViewById实际上,就是从DocView遍历树,找到对应的view):

public class ActivityViewFinder implements ViewFinder {@Overridepublic View findView(Object o, int resId) {return ((Activity) o).findViewById(resId);}
}

3、lib

这部分是整个框架的主题部分,我们需要想一下我们想要什么效果和需要做哪些东西:

想要做什么:简化控件的初始化,代码自动生成,使用时添加注解就可以完成

需要做的事情

  • 代码生成——创建AbstractProcessor的实现类重写方法
  • 注解——既然使用注解标注,首先眼获取标注注解的控件和资源Id
  • 类——拼接生成的类名并实现ViewBinder接口
  • 方法——重写ViewBinder的方法,在其中调用ViewFinder的findView方法

3.1、AbstractProcessor实现

@AutoService(Processor.class)
public class MYProcessor extends AbstractProcessor {private Filer mFiler;private Elements mElements;private Messager mMessage;private Map<String, AnnotatedClass> annotatedClassMap;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mFiler = processingEnv.getFiler();mElements = processingEnv.getElementUtils();mMessage = processingEnv.getMessager();annotatedClassMap = new TreeMap<>();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {......return true;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> types = new LinkedHashSet<>();types.add(BindView.class.getCanonicalName()); // 声明使用的注解return types;}
}

上面创建了AbstractProcessor并完成了初始化的操作,在getSupportedAnnotationTypes()方法中设置了要使用的注解为BindView。

3.2、注解的获取和解析

private void processBindView(RoundEnvironment roundEnv) {for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {AnnotatedClass annotatedClass = getAnnotatedClass(element);annotatedClass.addFields(new BindViewField(element));}}private AnnotatedClass getAnnotatedClass(Element element) {TypeElement typeElement = (TypeElement) element.getEnclosingElement();String fullName = element.getSimpleName().toString();AnnotatedClass annotatedClass = annotatedClassMap.get(fullName);if (annotatedClass == null) {annotatedClass = new AnnotatedClass(typeElement, mElements);annotatedClassMap.put(fullName, annotatedClass);}return annotatedClass;}

获取每个注解,将注解信息封装在AnnotatedClass中,将element封装在BindViewField对象中,同时使用Map缓存获取的信息,BindViewField保存了添加注解控件的名称和绑定资源的id:

public class BindViewField {private VariableElement variableElement;private int resId;BindViewField(Element element) {if (element.getKind() != ElementKind.FIELD) {return;}variableElement = (VariableElement) element;BindView bindView = variableElement.getAnnotation(BindView.class);resId = bindView.value();}Name getFiledName() {return variableElement.getSimpleName();}int getResId() {return resId;}TypeMirror getFieldType() {return variableElement.asType();}
}

AnnotatedClass保存了每个注解的typeElement, mElements和BindViewField 实例,即注解的所有信息都在其中,

 public AnnotatedClass(TypeElement mTypeElement, Elements mElements) {this.mTypeElement = mTypeElement;this.mElements = mElements;bindViewFields = new ArrayList<>();}public void addFields(BindViewField field) {bindViewFields.add(field);}

3.3、类文件

TypeSpec typeSpec = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewBinder").addModifiers(Modifier.PUBLIC).addSuperinterface(ParameterizedTypeName.get(TypeUtil.BINDER, TypeName.get(mTypeElement.asType()))).addMethod(bindViewMethod.build()).addMethod(unBindViewMethod.build()).build();String packName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();
JavaFile.builder(packName, typeSpec).build();           private static class TypeUtil {static final ClassName BINDER = ClassName.get("com.example.apilibrary", "ViewBinder");static final ClassName VIEW_FINDER = ClassName.get("com.example.apilibrary", "ViewFinder");}

上面代码操作:

  1. 拼接类名
  2. 设置修饰符
  3. 添加实现的接口
  4. 添加方法
  5. 生成Java文件

3.4、方法实现

绑定注解的方法

 MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addParameter(TypeName.get(mTypeElement.asType()), "host").addParameter(TypeName.OBJECT, "source").addParameter(TypeUtil.VIEW_FINDER, "finder");for (BindViewField field : bindViewFields) {bindViewMethod.addStatement("host.$N = ($T)(finder.findView(source,$L))", field.getFiledName(),ClassName.get(field.getFieldType()), field.getResId());}

上面代码方法的基本配置外,主要的是添加参数和内部方法,“host.N=(N = (N=(T)(finder.findView(source,$L))”,使用控件名称、类型和id替换后的代码为 host.button = (Button)(finder.findView(source,2131165223));

解除绑定方法

 MethodSpec.Builder unBindViewMethod = MethodSpec.methodBuilder("unBindView").addModifiers(Modifier.PUBLIC).addParameter(TypeName.get(mTypeElement.asType()), "host").addAnnotation(Override.class);for (BindViewField field : bindViewFields) {unBindViewMethod.addStatement("host.$N = null", field.getFiledName());}

到此根据注解生成代码部分就完成了,到此时只要你在Activity中使用@BindView注解就会生成相应的代码文件:

@BindView(R.id.button5)
Button button;

Rebuild项目后在build文件夹下就会生成代码了,查看生成的代码如下:

从类名和实现上与我们设置一致,在重写的bindView方法中,调用了ViewFinder实现类的findView方法,而在findView方法中使用了Android的findViewbyId(),所以控件的初始化就完成了。

4、绑定入口

public class BufferViewBinder {private static final ActivityViewFinder FINDER = new ActivityViewFinder(); // 创建ActivityViewFinder实例private static final Map<String, ViewBinder> BINDER_MAP = new HashMap<>();  // 创建缓存Mappublic static void bind(Activity activity) {bind(activity, activity, FINDER);} // 提供的绑定接口private static void bind(Object host, Object o, ViewFinder finder) {String className = host.getClass().getName();  // 获取绑定的类名ViewBinder viewBinder = BINDER_MAP.get(className);  // 查看是否有缓存if (viewBinder == null) {try {Class<?> aClass = Class.forName(className + "$$ViewBinder"); // 根据类名反射获取自动生成的类try {viewBinder = (ViewBinder) aClass.newInstance(); // 创建自动生成的类的实例BINDER_MAP.put(className, viewBinder);  // 添加到缓存if (viewBinder != null) {viewBinder.bindView(host, o, finder); // 调用绑定的方法}} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}} catch (ClassNotFoundException e) {e.printStackTrace();}}}public static void unBind(Object host) {String classname = host.getClass().getName();ViewBinder viewBinder = BINDER_MAP.get(classname);if (viewBinder != null) {viewBinder.unBindView(host); // 解除绑定}}
}

执行操作如下:

  1. 使用注解标注要初始化的控件,并传入初始化的Id
  2. 调用bind(activity) 绑定注解
  3. 跟剧绑定的Activity获取生成的类名,使用反射获取类并创建实例
  4. 调用bindView()方法,完成控件的初始化

5、使用

public class BindActivity extends AppCompatActivity {@BindView(R.id.button5)Button button;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_bind);BufferViewBinder.bind(this);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(BindActivity.this,"Button",Toast.LENGTH_SHORT).show();}});}@Overrideprotected void onDestroy() {super.onDestroy();BufferViewBinder.unBind(this);}
}

运行效果:


Java注解代码生成

Java、Android注解代码生成(ButterKnife原理、ViewBinding)相关推荐

  1. 玩转java(Android)注解

    2019独角兽企业重金招聘Python工程师标准>>> 玩转java(Android)注解 1. java标准(原生)注解概览 Java API 中,在java.lang.java. ...

  2. android bind 自动声明控件,Android注解神器ButterKnife使用说明

    阅读本文大概需要5分钟 前言 如果你还在一行一行的手写findViewById的话,只能证明你对新技术的敏感度太差,间接地暴露了你不善于接受新事物的性格特征,太过于因循守旧.按部就班,这对于一个程序员 ...

  3. java中注解动态传参_Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)...

    Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)java 前言:因为前段时间忙于写接口,在接口中须要作不少的参数校验,本着简洁.高效的原则,便写了这个小工具供本身使 ...

  4. Android RollBack机制实现原理剖析

    功能介绍: 在Android 10.0中,Google新增加了个功能. 如果用户对新升级的APP不满意,可以通过"回到过去",回滚到旧版. 当然,如果新安装的apk出现了各种问题无 ...

  5. Android之Butterknife原理解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 Butterknife是一个专注于Android系统的View注入框架, ...

  6. 【Android APT】编译时技术 ( ButterKnife 原理分析 )

    文章目录 一.编译时技术简介 二.ButterKnife 原理分析 二.ButterKnife 生成 Activity_ViewBinding 代码分析 一.编译时技术简介 APT ( Annotat ...

  7. Android 注解Annotation及在流行框架中使用的原理

    前言 Annotation--注解,JDK1.5新增加的功能.它能够添加到 Java 源代码的语法元数据.类.方法.变量.参数.包都可以被注解,可用来将信息元数据与程序元素进行关联.目前很多开源库都使 ...

  8. 浅谈Java/Android下的注解

    什么是注解 java.lang.annotation,接口 Annotation,在JDK5.0及以后版本引入. 注解是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行相应的处理.通 ...

  9. Android 兼容 Java 8 语法特性的原理分析

    本文主要阐述了Lambda表达式及其底层实现(invokedynamic指令)的原理.Android第三方插件RetroLambda对其的支持过程.Android官方最新的dex编译器D8对其的编译支 ...

最新文章

  1. tomcat的webapps下没有出现配置过的文件夹
  2. 用它5分钟以后,我放弃用了四年的 Flask
  3. 微软私有云系列----证书配置
  4. telnet给服务器发消息,Telnet按字符发送字符串
  5. 【Python】20个Pandas数据实战案例,干货多多
  6. 如何判断 ios设备的类型(iphone,ipod,ipad)
  7. 无限超越超级机器人nds_阿里重新定义个人电脑!仅名片大小,无限升级,不怕丢失无惧病毒,价格仅传统PC一半...
  8. julia语言 python解释器_继 Python 解释器移植到 Firefox 后,Mozilla 现在想支持 Julia 和 R...
  9. ASP.NET2.0中实现图像转换过滤效果
  10. 关于vue使用print.js打印会有一个空白页的问题
  11. Wireshark流量分析
  12. 请没有买房和买车的朋友一定认真的看一下(转)
  13. HDOJ 5296 Annoying problem LCA+数据结构
  14. Android Studio:activity界面跳转时闪退或报错:xxx keeps stopping
  15. 连锁加盟2-3事~实录
  16. 【更好用的单片机】Stduino学习(三十三)面包板模块
  17. 【知识】快乐物质:多巴胺和内啡肽(内酚酞)的区别
  18. MATLAB的符号运算基础
  19. 财路网每日原创推送:2019年区块链在企业应用中扮演的角色
  20. js + leetcode刷题:No.914 卡牌分组

热门文章

  1. 鸿蒙系统今日发布 中国人自己的操作系统,中国人自己的操作系统,有望取代谷歌安卓的地位,但面临一个难题...
  2. nslookup命令用法
  3. java 对象 php对象_java对象是什么?
  4. 1600802071
  5. EOS账户和钱包综合指南
  6. 机器学习——利用K-均值聚类算法对未标注数据分组
  7. 如何开发auto complete 智能提示功能
  8. GroovyQ | 关注Groovy社区动态,分享Groovy开发经验。
  9. 我的博客博客之路....
  10. smartfoxserver 个人心得