转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/43452969 ,本文出自: 【张鸿洋的博客】

1、概述

记得很久以前,写过几篇博客,容我列举一下:

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)

Android 框架炼成 教你如何写组件间通信框架EventBus

大家可以关注下这些博客的评论,不管咋样,大家对于性能的考虑还是很多呢,一看到这类的框架,不解析还好,只要解析出来是注解和反射,必然的一个问题就是:这样会不会影响性能呀?嗯,肯定会有性能的损耗,那么我就再考虑有没有更好的实现方式呢?既可以实现注入,还能保证性能无损耗呢?好消息来说,是有哒,对没错,上述博客实现方式,包括xutils , afinal 目前的注入使用的都是运行时注解,当然了还有一类注解叫做编译时注解。

2、注解

说道注解,竟然还有各种分类,得,这记不住,我们从注解的作用来反推其分类,帮助大家记忆,然后举例强化大家的记忆,话说注解的作用:

1、标记一些信息,这么说可能太抽象,那么我说,你见过@Override、@SuppressWarnings等,这类注解就是用于标识,可以用作一些检验

2、运行时动态处理,这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。类似上述三篇博文中的做法。

3、编译时动态处理,这个呢?就是我们今天的主角了,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~

关于3,大家不明白,没事,下文会详谈,使用这类注解的项目有:ParcelableGenerator、butterknife 、androidannotaion等。

作用谈完了,那么如果你看到一个注解的声明你如何去判断他的作用呢?例如:

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

1秒钟告诉我,它的作用是什么?哈,大家可能会郁闷,擦,我咋知道。其实可以看这个注解上面的@Retention后面的值,设置的为CLASS,说明就是编译时动态处理的。

这个值是一个枚举:有三个:SOURCE、RUNTIME、CLASS , 到这里,是不是,搜噶,这三个11对应于上面三个作用。

好了,说完了注解的作用以及判断方式,那么大家可以看到除了@Retention还有个@Target,@Target的值呢是一个ElementType[]数组。什么意思呢?就是标明这个注解能标识哪些东西,比如类、变量、方法、甚至是注解本身(元注解)等。这个在:Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)有详细说明。

好了,到此注解告一段落,大家只要记得注解的作用,以及如何去定义一个注解就好。

接下来进入我们的主题编译时注解。

对了,我创建了一个公众号,会推送一些开源项目、最新博客、视频等,关于博客涉及到的东西,也会提前给大家通知,可以关注一下,谢谢,左侧栏目,微信扫描即可。

3、编译时注解

那我们说一下编写过程。

1、创建一个类,继承AbstractProcessor

package com.zhy.util.ioc.processor;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.RoundEnvironment;import javax.annotation.processing.SupportedAnnotationTypes;import javax.annotation.processing.SupportedSourceVersion;import javax.lang.model.SourceVersion;import javax.lang.model.element.TypeElement;@SupportedAnnotationTypes("com.zhy.util.ioc.annotation.InjectView")@SupportedSourceVersion(SourceVersion.RELEASE_6)public class ViewInjectProcessorBeta extends AbstractProcessor{@Overridepublic boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv){// TODO Auto-generated method stubreturn false;}}

这个类上可以添加注解:

@SupportedAnnotationTypes的值为当前类支持的注解的完整类路径,支持通配符。

@SupportedSourceVersion 标识该处理器支持的源码版本

除此以外还有一个@SupportedOptions,这个一般是命令行时候用的,设置一些选项,but,命令行我不熟,因此:略。

注:如果大家找不到AbstractProcessor,记得右键build-path add library把jdk加进来。


2、创建resources等文件。

这个对项目的一个结构有着固定的要求,下面我通过一张图来说:


可以看到,在我们的项目中呢,还需要创建一个resources这样的source folder ,右键 new sources folder即可。

然后在里面创建META-INF/services/javax.annotation.processing.Processor文件,这个文件中去写我们处理器的类完整路径。

经过上述两部,我们的编写环境就OK了。


4、完整例子

下面我们通过一个例子来给大家演示编译时动态生成数据,我们的效果是这样的,用户编写一堆bean,例如User类,我们通过注解提取属性动态生成一个json文件,以及一个代理类,注意是编译时生成。

注:以下为一个教学示例,无任何使用价值。

那么我们依然分为步骤来做:

1、创建编写环境


javax.annotation.processing.Processor里面写的是:com.zhy.annotationprocess.processor.BeanProcessor

我们还创建了一个注解:

package com.zhy.annotationprocess.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ ElementType.FIELD, ElementType.TYPE })@Retention(RetentionPolicy.CLASS)public @interface Seriable{}

哈,一秒钟告诉我,哪一类作用的注解。

2、动态生成数据

1、首先明确一下我们的目标:

我们有很多bean类,例如:

public class User{@Seriableprivate String username;@Seriableprivate String password;private String three;private String four;}

@Seriablepublic class Article{private String title ; private String content ; }

看到有两个普通的bean,上面声明了我们的注解,如果类上声明注解我们就将其所有的变量都生成一个json描述文件;如果仅仅是成员变量呢?那我们只提取声明的成员变量来动态生成。

类似如下的描述文件:

{class:"com.zhy.Article", fields: {  content:"java.lang.String",  title:"java.lang.String" }}

是不是觉得没撒用处,其实用处大大滴,以后我们会验证。

2、编写BeanProcessor

package com.zhy.annotationprocess.processor;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.ProcessingEnvironment;import javax.annotation.processing.RoundEnvironment;import javax.annotation.processing.SupportedAnnotationTypes;import javax.annotation.processing.SupportedSourceVersion;import javax.lang.model.SourceVersion;import javax.lang.model.element.Element;import javax.lang.model.element.ElementKind;import javax.lang.model.element.TypeElement;import javax.lang.model.element.VariableElement;import javax.lang.model.util.ElementFilter;import javax.lang.model.util.Elements;import com.zhy.annotationprocess.annotation.Seriable;@SupportedAnnotationTypes("com.zhy.annotationprocess.annotation.Seriable")@SupportedSourceVersion(SourceVersion.RELEASE_6)public class BeanProcessor extends AbstractProcessor{ // 元素操作的辅助类Elements elementUtils;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv){super.init(processingEnv);// 元素操作的辅助类elementUtils = processingEnv.getElementUtils();}@Overridepublic boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv){// 获得被该注解声明的元素Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);TypeElement classElement = null;// 声明类元素List<VariableElement> fields = null;// 声明一个存放成员变量的列表// 存放二者Map<String, List<VariableElement>> maps = new HashMap<String, List<VariableElement>>();// 遍历for (Element ele : elememts){// 判断该元素是否为类if (ele.getKind() == ElementKind.CLASS){classElement = (TypeElement) ele;maps.put(classElement.getQualifiedName().toString(),fields = new ArrayList<VariableElement>());} else if (ele.getKind() == ElementKind.FIELD) // 判断该元素是否为成员变量{VariableElement varELe = (VariableElement) ele;// 获取该元素封装类型TypeElement enclosingElement = (TypeElement) varELe.getEnclosingElement();// 拿到keyString key = enclosingElement.getQualifiedName().toString();fields = maps.get(key);if (fields == null){maps.put(key, fields = new ArrayList<VariableElement>());}fields.add(varELe);}}for (String key : maps.keySet()){if (maps.get(key).size() == 0){TypeElement typeElement = elementUtils.getTypeElement(key);List<? extends Element> allMembers = elementUtils.getAllMembers(typeElement);if (allMembers.size() > 0){maps.get(key).addAll(ElementFilter.fieldsIn(allMembers));}}}generateCodes(maps);return true;}private void generateCodes(Map<String, List<VariableElement>> maps){File dir = new File("f://apt_test");if (!dir.exists())dir.mkdirs();// 遍历mapfor (String key : maps.keySet()){// 创建文件File file = new File(dir, key.replaceAll("\\.", "_") + ".txt");try{/** * 编写json文件内容 */FileWriter fw = new FileWriter(file);fw.append("{").append("class:").append("\"" + key + "\"").append(",\n ");fw.append("fields:\n {\n");List<VariableElement> fields = maps.get(key);for (int i = 0; i < fields.size(); i++){VariableElement field = fields.get(i);fw.append("  ").append(field.getSimpleName()).append(":").append("\"" + field.asType().toString() + "\"");if (i < fields.size() - 1){fw.append(",");fw.append("\n");}}fw.append("\n }\n");fw.append("}");fw.flush();fw.close();} catch (IOException e){e.printStackTrace();}}}}

代码略长,但是注释很清除,我来解释一下,基本分为两个过程:1、找出标识注解的类或成员变量,封装到maps中;2、遍历maps为每个类创建json文件。我们把文件输出到了f://apt_test文件夹中,如果你没有f盘神马的,自行修改目录。

3、使用

到此,我们写完了~~那么如何用呢?

1、导出jar

为了更好的演示,以及省篇幅,我录成gif


注意我选择的一些复选框,和一些默认复选框的选中状态,我将其放在桌面上~~

2、新建一个android或java项目

将jar拷贝到libs下,如果是java项目,需要自己创建lib文件夹,自己手动引用。

然后就开始编写bean吧:我这里就写了两个类,一个User,一个Article,上面贴过代码了。

3、启用annotation processor

这里我是eclipse,大家如果是maven项目或者是别的什么IDE,自行进行网络搜索,这里有个Android Studio下的使用,自己点击哈,其实命令行也可以。

下面我们eclipse依然是个gif,不然得截一堆图片:

假设我们的jar已经拷贝到项目中了,进行如下操作



操作完成以后,那么就可以去f://apt_test中


打开即可看到:

{class:"com.zhy.User", fields: {  username:"java.lang.String",  password:"java.lang.String" }}

{class:"com.zhy.Article", fields: {  content:"java.lang.String",  title:"java.lang.String" }}

ok,这样的话,我们一个简单的annotation processor的教程就搞定了~~如果想学,一定要去试,各种试,不要怕麻烦,要是简单谁都会,那还有什么意义~~

这是一个非常简单的例子,那么具体到我们的项目中如何使用呢?鉴于篇幅,可能只能在下一篇给大家继续了。不过库的雏形已经形成:

5、HyViewInject

ok,这就是基于上述的一个库,主要用于Android的控件的注入,类似butterknife,尚在完善中,欢迎大家使用,fork or star ,我们一起完善。

sample的效果图:

第一个Activity中一个TextView和ListView,第二个Activity一个TextView和Fragment,主要测试了Activity、Fragment、Adapter中注入控件。


github地址:点击传送,尚在完善中,欢迎大家使用,fork or star ,我们一起完善。


群号:423372824


博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

视频目录地址:本人录制的视频教程

















更多相关文章

  1. NPM 和webpack 的基础使用
  2. 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
  3. android解析xml文件的方式
  4. Android(安卓)- 文件读写操作 总结
  5. Android(安卓)开发学习 HelloAndroid例子
  6. Android中JNI的使用方法
  7. Android体系结构分析
  8. Android(安卓)Studio打包生成Jar包的方法(亲测可用)
  9. Android(安卓)SDK Content loader has encountered a problem

随机推荐

  1. MySQL系列之二 多实例配置
  2. MySQL系列之十三 MySQL的复制
  3. MySQL系列之六 用户与授权
  4. MySQL系列之开篇 MySQL关系型数据库基础
  5. MySQL系列之五 视图、存储函数、存储过程
  6. MySQL在Windows中net start mysql 启动My
  7. MySql 缓存查询原理与缓存监控和索引监控
  8. MySQL系列之八 MySQL服务器变量
  9. MySQL系列之九 mysql查询缓存及索引
  10. 解决MySQL启动报错:ERROR 2003 (HY000):