美文网首页Android CommunityAndroid开发程序员
Android 自定义编译时注解1 - 简单的例子

Android 自定义编译时注解1 - 简单的例子

作者: 程序员徐公 | 来源:发表于2017-04-20 21:46 被阅读726次

    为什么要写这一系列的博客呢?

    因为在 Android 开发的过程中, 泛型,反射,注解这些知识进场会用到,几乎所有的框架至少都会用到上面的一两种知识,如 Gson 就用到泛型,反射,注解,Retrofit 也用到泛型,反射,注解 。学好这些知识对我们进阶非常重要,尤其是阅读开源框架源码或者自己开发开源框架。

    java Type 详解

    java 反射机制详解

    注解使用入门(一)

    Android 自定义编译时注解1 - 简单的例子

    前言

    记得去年的时候写过一篇博客 注解使用入门(一),这篇博客主要介绍了注解的一些基本知识,以及基于运行时注解的 Demo。今天这篇博客主要介绍怎样编写编译时注解的Demo。

    这篇博客代码参考了鸿洋的博客: Android 打造编译时注解解析框架 这只是一个开始

    注解的重要知识

    我们先复习一下注解的一些重要知识:

    根据注解使用方法和用途,我们可以将Annotation分为三类:

    1. JDK内置系统注解,如 @Override 等
    2. 元注解
    3. 自定义注解,我们自己实现的自定义注解

    元注解:

    元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。Java5.0定义的元注解:

    1. @Target
    2. @Retention
    3. @Documented
    4. @Inherited

    元注解 解析说明

    • @Documented 是否会保存到 Javadoc 文档中

    • @Retention 保留时间,可选值

    SOURCE(源码时),CLASS(编译时),RUNTIME(运行时)

    默认为 CLASS,SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, SuppressWarnings

    • @Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有

    ANONOTATION_TYPE(注解类型声明),
    PACKAGE(包)
    TYPE (类,包括enum及接口,注解类型)
    METHOD (方法)
    CONSTRUCTOR (构造方法)
    FIFLD (成员变量)
    PARAMATER (参数)
    LOCAL_VARIABLE (局部 变量)

    • @Inherited 是否可以被继承,默认为 false

    编译时注解例子说明

    这里我们以 AndroidStudio 为例子讲解。假设我们要把 User 这样的一个类,在编译时转化成类似于 json 这样键值对的形式。大概需要三步。

    public class Person {
        @Seriable()
        String name;
        @Seriable()
        String area;
        @Seriable()
        int age;
        int weight;
    
        @Seriable()
        List<Article> mArticleList;
    }
    
    {class:"xj.jsonlibdemo.Person",
     fields:
     {
      name:"java.lang.String",
      area:"java.lang.String",
      age:"int",
      mArticleList:"java.util.List<xj.jsonlibdemo.Article>"
     }
    }
    

    第一步:我们新建一个 java library,搭配好相关的配置,并编写我们自定义的 Animation Seriable,如下所示

    首先:我们新建一个 java library:

    接着: 编写我们的自定义注解

    @Documented()
    // 表示是基于编译时注解的
    @Retention(RetentionPolicy.CLASS)
    // 表示可以作用于成员变量,类、接口
    @Target({ElementType.FIELD, ElementType.TYPE}) 
    public @interface Seriable {
    
    }
    
    

    如果对元注解还步了解的话,建议先阅读我之前写的博客 注解使用入门(一),这里不再讲解

    最后:在 resources/META-INF/services/javax.annotation.processing.Processor 文件中 添加 我们自定义注解的全限定路径 com.example.JsonProcessor。注意若 resources/META-INF/services/javax.annotation.processing.Processor 不存在,需要自己添加。

    第二步:编写我们的解析器,继承 AbstractProcessor ,并重写 process 方法,处理相关逻辑。

    @SupportedAnnotationTypes({"com.example.Seriable"})
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class JsonProcessor extends AbstractProcessor {
    
        private Elements mElementUtils;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            //  工具辅助类
            mElementUtils = processingEnv.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            //   第一步,根据我们自定义的注解拿到 elememts set 集合
            Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Seriable.class);
            TypeElement typeElement;
            VariableElement variableElement;
            Map<String, List<VariableElement>> map = new HashMap<>();
            List<VariableElement> fileds = null;
            //  第二步: 根据 element 的类型做相应的处理,并存进 map 集合
            for (Element element : elememts) {
                ElementKind kind = element.getKind();
                // 判断该元素是否为类
                if (kind == ElementKind.CLASS) {
                    typeElement = (TypeElement) element;
                    //  这里以类的全限定类名作为 key,确保唯一
                    String qualifiedName = typeElement.getQualifiedName().toString();
                    map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                    // 判断该元素是否为成员变量
                } else if (kind == ElementKind.FIELD) {
                    variableElement = (VariableElement) element;
                    //                获取该元素的封装类型
                    typeElement = (TypeElement) variableElement.getEnclosingElement();
                    String qualifiedName = typeElement.getQualifiedName().toString();
                    fileds = map.get(qualifiedName);
                    if (fileds == null) {
                        map.put(qualifiedName, fileds = new ArrayList<VariableElement>());
                    }
                    fileds.add(variableElement);
                }
            }
    
            Set<String> set = map.keySet();
    
            for (String key : set) {
                if (map.get(key).size() == 0) {
                    typeElement = mElementUtils.getTypeElement(key);
                    List<? extends Element> allMembers = mElementUtils.getAllMembers(typeElement);
                    if (allMembers.size() > 0) {
                        map.get(key).addAll(ElementFilter.fieldsIn(allMembers));
                    }
                }
            }
            // 第三步:根据 map 集合数据生成代码
            generateCodes(map);
    
            return true;
        }
    
        // 生成我们的代码文件
        private void generateCodes(Map<String, List<VariableElement>> maps) {
            File dir = new File("f://Animation");
            if (!dir.exists())
                dir.mkdirs();
            // 遍历map
            for (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();
                }
    
            }
        }
    }
    
    

    思路解析

    • 第一步,根据我们自定义的注解拿到 elememts set 集合
    • 第二步:根据 elememt 的类型做相应的处理,并存进 map 集合
    • 第三步:根据 map 集合的数据,生成相应的代码。

    第三步:调用 gradle build 命令生成 jar 包

    在 AndroidStudio 中的 Terminal 窗口输入 gradle build 命令,完成之后将生成 jar 包。我们就可以使用这一个 jar 包了。需要注意的是 我们需要将 gradle 添加到环境变量中。

    第四步,将我们生成的 jar 包复制到 moudle 目录下,compile fileTree(dir: 'libs', include: ['*.jar']) , 就可以使用了。

    比如我们新建一个 moudle,新建两个类,如下:

    @Seriable
    public class Article {
        private String title;
        private String content;
    }
    
    public class User {
        @Seriable()
        String name;
        @Seriable()
        String area;
        @Seriable()
        int age;
        int weight;
    
        @Seriable()
        List<Article> mArticleList;
    }
    

    在 moudle 的目录下执行 gradle build 命令,将可以在我们的保存路径中看到我们生成的两个文件,(这个路径是我们前面在编写 JsonProcessor 缩写的,File dir = new File("f://Animation");)

    {class:"xj.jsonlibdemo.Article",
     fields:
     {
      title:"java.lang.String",
      content:"java.lang.String",
      time:"long"
     }
    }
    
    {class:"xj.jsonlibdemo.Person",
     fields:
     {
      name:"java.lang.String",
      area:"java.lang.String",
      age:"int",
      mArticleList:"java.util.List<xj.jsonlibdemo.Article>"
     }
    }
    

    到此,一个简单的例子讲解完毕:


    参考博客:

    Android 打造编译时注解解析框架 这只是一个开始

    github 地址

    相关文章

      网友评论

        本文标题:Android 自定义编译时注解1 - 简单的例子

        本文链接:https://www.haomeiwen.com/subject/aehrzttx.html