自定义注解框架实现
来源:互联网 发布:美女适合当程序员吗 编辑:程序博客网 时间:2024/06/05 09:51
日常项目开发中,注解使用越来越广泛,我们会经常用到各类注解框架为我们减轻工作中的一些重复劳动,比如AndroidAnnotation、Dagger2、ButterKnife等这些大名鼎鼎的框架。这些框架有一个共同点就是使用编译时生成代码代替反射,大大优化了性能。
那为什么一个小小的注解@BindView就可以实现view的查找功能呢?本文就来一探究竟,打造一个类似ButterKnife简单实现view绑定的注解框架。
在开始之前,我们先认识一项关键技术APT(Annotation Processing Tool)。官方解释:APT是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。简单的说就是APT在编译时把注解生成代码,关于APT更多知识此处不展开,有兴趣可以百度查看相关资料。
伴随着去年Android Gradle 插件 2.2版本发布,android-apt的作者在官网发表声明后续将不会继续维护android-apt,并推荐大家使用 Android 官方插件annotationProcessor。不过由于很多框架还是用的APT,本文还是基于APT实现。
一、创建工程
首先在Android studio里新建一个Android工程APTDemo。为了使用android-apt插件,需要在工程的build.gradle中加入依赖。
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
二、创建 annotation Module
新建一个Java Library Module,命名annotation,然后创建一个BindView注解类。
@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS) public @interface BindView { int value() default 0;}
BindView的target是FIELD,只对成员变量进行注解,有一个int类型的参数。默认值为0,用来传入view的Id。
build.gradle,采用默认就好。
apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])}sourceCompatibility = "1.7"targetCompatibility = "1.7"
三、创建 compiler Module
新建一个Java Library Module,命名compiler。
build.gradle
apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.auto.service:auto-service:1.0-rc2' compile 'com.squareup:javapoet:1.7.0' compile project(':annotation')}sourceCompatibility = "1.7"targetCompatibility = "1.7"
依赖了annotation模块,因为要使用annotation中定义的Bindview注解,另外引入了auto-service和javapoet库。auto-service ,主要用于注解 Processor,对其生成 META-INF 配置信息。javapoet可以通过预先设置好的规则,自动生成Java 代码文件,这个真是个好东西。
新建BindViewProcesor类,这个类就是整个注解框架的核心,包括自动生成代码等。
@AutoService(Processor.class)public class BindViewProcesor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { return false; }}
下面我们来编辑BindViewProcesor类,主要逻辑都在process方法中。
为了一些工具类的方便使用,重写父类的init方法。
private Elements elements; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elements = processingEnvironment.getElementUtils(); filer = processingEnvironment.getFiler(); }
Elements是元素操作相关的辅助类,主要用于获取各种元素,结构类似DOM树。Filer是文件操作的辅助类。parentAndChildMap是一个map,用来存放类与方法的对应关系。
修改getSupportedAnnotationTypes,指定可以被注解处理器处理的类型,这里是BindView.class。
@Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(BindView.class.getCanonicalName()); }
另外还有一个指定java版本的方法getSupportedSourceVersion,这里我们使用注解@SupportedSourceVersion(SourceVersion.RELEASE_7)。
下面就是核心方法process的修改,大致的步骤如下:
1、获取所有标注了@BindView注解的的Element。
2、遍历标注了注解的Element集合,获取每一个Element的父元素,由于@BindView的Target是FIELD,那么父元素就是该FIELD的类即TypeElement。当然由于一个类中可能有多个标记了@BindView的字段,此处用HashMap来存放之间的对应关系。
3、遍历HashMap,通过javapoet生成目标类。先指定MethodSpec的生成规则,接着指定TypeSpec和JavaFile的规则,最后调用javaFile.writeTo(filer)生成Java文件。
for (Map.Entry<TypeElement, List<Element>> entry : parentAndChildMap.entrySet()) { TypeElement typeElement = entry.getKey(); MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bindView").addModifiers(Modifier.PUBLIC, Modifier.STATIC).returns(void.class).addParameter(ClassName.get(typeElement.asType()), "activity"); List<Element> childElementList = entry.getValue(); for (Element element : childElementList) { int id = element.getAnnotation(BindView.class).value(); String statement = String.format("activity.%s = (%s)activity.findViewById(%d)", element.getSimpleName (), ClassName.get(element.asType()).toString(), id); methodSpecBuilder.addStatement(statement); } TypeSpec typeSpec = TypeSpec.classBuilder("BindView$$" + typeElement.getSimpleName()).addModifiers (Modifier.PUBLIC, Modifier.FINAL).superclass(ClassName.get(typeElement.asType())).addMethod (methodSpecBuilder.build()).build(); JavaFile javaFile = JavaFile.builder(elements.getPackageOf(typeElement).getQualifiedName().toString(), typeSpec).build(); try { javaFile.writeTo(filer); } catch (Exception e) { e.printStackTrace(); } }
具体javapoet的使用方法,可以参见Javapoet源码。后面有时间再单独写一篇关于javapoet的。
完整的代码如下:
@AutoService(Processor.class)@SupportedSourceVersion(SourceVersion.RELEASE_7)public class BindViewProcesor extends AbstractProcessor { private Elements elements; private Filer filer; private HashMap<TypeElement,List<Element>> parentAndChildMap; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elements = processingEnvironment.getElementUtils(); filer = processingEnvironment.getFiler(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) { Set<? extends Element> fieldElements = roundEnv.getElementsAnnotatedWith(BindView.class); if (fieldElements == null) { return false; } parentAndChildMap = new LinkedHashMap<>(); for (Element fieldEle : fieldElements) { TypeElement typeElement = (TypeElement) fieldEle.getEnclosingElement(); if (parentAndChildMap.containsKey(typeElement)) { parentAndChildMap.get(typeElement).add(fieldEle); } else { List<Element> childEleList = new ArrayList<>(); childEleList.add(fieldEle); parentAndChildMap.put(typeElement, childEleList); } } for(Map.Entry<TypeElement,List<Element>> entry:parentAndChildMap.entrySet()){ TypeElement typeElement = entry.getKey(); MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bindView") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(ClassName.get(typeElement.asType()), "activity"); List<Element> childElementList = entry.getValue(); for(Element element:childElementList){ int id = element.getAnnotation(BindView.class).value(); String statement = String.format("activity.%s = (%s)activity.findViewById(%d)", element .getSimpleName(), ClassName.get(element.asType()).toString(), id); methodSpecBuilder.addStatement(statement); } TypeSpec typeSpec = TypeSpec.classBuilder("BindView$$" + typeElement.getSimpleName()).addModifiers(Modifier .PUBLIC, Modifier.FINAL).superclass(ClassName.get(typeElement.asType())).addMethod(methodSpecBuilder.build()) .build(); JavaFile javaFile = JavaFile.builder(elements.getPackageOf(typeElement).getQualifiedName().toString(), typeSpec).build(); try { javaFile.writeTo(filer); } catch (Exception e) { e.printStackTrace(); } } return true; } @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(BindView.class.getCanonicalName()); }}
四、使用注解
为了在app模块中使用注解,需要在app的build.gradle配置注解的依赖。
compile project(':compiler')
新建一个MainActivity,在textview上标记注解@BindView(R.id.text)
public class MainActivity extends AppCompatActivity { @BindView(R.id.text) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
然后编译一下整个工程,编译完成之后,会在app/build/generated/source/apt/debug/目录下自动生成BindView$$MainActivity类。
public final class BindView$$MainActivity extends MainActivity { public static void bindView(MainActivity activity) { activity.textView = (android.widget.TextView)activity.findViewById(2131427415); }}
这个就是compiler中根据注解按照设定的规则使用Javapoet自动生成的。如果不知道在compiler中怎样重写process方法,我们可以先手动写出BindView$$MainActivity类,然后参考这个类再去想我们该怎样写自动生成代码的规则,这样会简单很多。
此时MainActivity中textview还没有初始化,需要在onCreate方法中调用BindView$$MainActivity.bindView(this)进行注册。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BindView$$MainActivity.bindView(this); textView.setText("Hello World!"); }
此时就完成了通过一个@BindView注解实现view的初始化的目的,整个流程都是套路,具体实现就是compiler中的process方法了。当然本文中的自定义框架只是初步的实现了@BindView的功能,如果想要完整的实现类似ButterKnife的功能,可以参考ButterKnife的源码。
- 自定义注解框架实现
- Android 自定义注解框架
- 实现基于注解(Annotation)的数据库框架(三)自定义注解(Annotation)
- SpringAOP实现自定义注解
- Android ViewUtils注解框架自定义
- java 自定义注解 spring aop 实现注解
- 基于Spring MVC框架JSR-303的自定义注解Validator验证实现
- 基于Spring MVC框架JSR-303的自定义注解Validator验证实现
- SpringMVC验证框架Validation自定义注解实现传递参数到国际化资源文件
- SpringMVC验证框架Validation自定义注解实现传递参数到国际化资源文件
- SpringMVC验证框架Validation自定义注解实现传递参数到国际化资源文件
- java中实现自定义注解
- Java 自定义注解 源码实现
- Java自定义注解的实现
- 自定义注解,实现拦截器
- Java自定义注解实现过程
- spring自定义注解AOP实现
- 初试注解 自定义实现FindViewById
- Ripple安全操作系列一:使用…
- 2013年总结
- VML绘制圆角矩形的方法
- excle文件转为shp文件说明
- 大型网站架构演化
- 自定义注解框架实现
- 七大室内定位技术PK(转自3Snews)
- HDU 2504.又见GCD
- 还原系统默认的.cur文件
- Quick Translator
- TileStache 介绍
- Java timer timertask用法(转)
- php在eclipse+tomcat+mysql环境下运行
- GB/T-28181平台级联实现方式