【Android APT】注解处理器 ( Element 注解节点相关操作 )

举报
韩曙亮 发表于 2022/01/11 01:09:04 2022/01/11
【摘要】 文章目录 一、获取被 注解 标注的节点二、Element 注解节点类型三、VariableElement 注解节点相关操作四、注解处理器 完整代码示例五、博客资源 Android APT...


Android APT 学习进阶路径 : 推荐按照顺序阅读 , 从零基础到开发简易 ButterKnife 注解框架的学习路径 ;


上一篇博客 【Android APT】注解处理器 ( 配置注解依赖、支持的注解类型、Java 版本支持 ) 中 为 注解处理器 Module 添加了 编译时注解 Module 依赖 , 并设置了支持该注解处理器 支持的 注解类型 , 和 支持的 Java 版本 ;

本篇博客开发 注解处理器 的 处理注解 , 生成代码的核心逻辑 ;





一、获取被 注解 标注的节点



处理注解的核心逻辑在 AbstractProcessor 中的 process 方法中实现 ;

@AutoService(Processor.class)
public class Compiler extends AbstractProcessor {
    /**
     * 搜索 Android 代码中的 BindView 注解
     * 并生成相关代码
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

先获取被注解标注的节点 , 搜索 BindView , 调用 process 方法的 RoundEnvironment roundEnv 参数的 getElementsAnnotatedWith 方法 , 即可搜索到整个 Module 中所有使用了 BindView 注解的元素 ;

// 搜索 BindView , 将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素
// 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;
// 通过 getElementsAnnotatedWith 方法可以搜索到整个 Module 中所有使用了 BindView 注解的元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

  
 
  • 1
  • 2
  • 3
  • 4

将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素 , 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;


在 app 模块中 , 只有 MainActivity 中的一个 属性字段 使用 BindView 注解 , 调用 roundEnv.getElementsAnnotatedWith(BindView.class) 方法获取的元素就是 MainActivity 中的 TextView hello 成员变量 ;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import kim.hsl.annotation.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.hello)
    TextView hello;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

假设在 若干 Activity 中 , 若干位置 , 使用了 BindView 注解 , 那么在获取的所有使用了 BindView 注解的字段 Set<? extends Element> elements 中装载了所有的使用了该注解的字段 , 这些字段来自不同的 Activity 中 ;

这就需要将 Set<? extends Element> elements 中的 字段 按照 Activity 上下文进行分组 , 以便生成代码 ;

这样每个 Activity 界面都对应若干个 Set<? extends Element> elements 中的元素 ;





二、Element 注解节点类型



使用注解标注的 Element 节点类型 :

ExecutableElement : 使用注解的 方法 节点类型 ;

VariableElement : 使用注解的 字段 节点类型 , 类的成员变量 ;

TypeElement : 使用注解的 类 节点类型 ;

PackageElement : 使用注解的 包 节点类型 ;


上述 4 4 4 个类都是 javax.lang.model.element.Element 的子类 ;


@BindView 注解标注的都是 Activity 中的成员字段 , 当前获取 Set<? extends Element> elements 集合中的节点都是 Field 字段对应的 VariableElement 类型的节点 ;





三、VariableElement 注解节点相关操作



遍历上述 VariableElement 类型节点集合 , 将其中的元素按照 Activity 进行分组 , 将分组结果放到 HashMap<String, HashSet<VariableElement>> elementMap 键值对 Map 集合中 ;


要分组放置 注解节点 的 HashMap<String, HashSet<VariableElement>> elementMap 键值对 Map 集合 , 其中 " 键 " 是 注解标注的成员字段所在的 Activity 类的全类名 , " 值 " 是该 Activity 中所有使用 @BindView 注解的成员字段集合 ;

// @BindView 注解标注的都是 Activity 中的成员字段,
// 上述 elements 中的元素都是 VariableElement 类型的节点
HashMap<String, HashSet<VariableElement>> elementMap = new HashMap<>();

  
 
  • 1
  • 2
  • 3

给定 VariableElement 注解节点 , 获取该节点的上一级节点 , 注解节点是 VariableElement , 成员字段节点 , 其上一级节点是就是 Activity 类对应的 类节点 TypeElement , 通过调用 VariableElement.getEnclosingElement 方法获取上一级节点 , 类型是 TypeElement ;

// 将注解节点类型强转为 VariableElement 类型
VariableElement ve = (VariableElement) element;

// 获取该注解节点对应的成员变量类名
// 先获取该注解节点的上一级节点 , 注解节点是 VariableElement , 成员字段节点
// 上一级节点是就是 Activity 类节点对应的 类节点 TypeElement
TypeElement te = (TypeElement) ve.getEnclosingElement();

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

获取 TypeElement 注解节点 的全类名 , 调用 TypeElement.getQualifiedName 方法获取 , 如果只获取类名 , 不包含完整包名的话 , 调用 TypeElement.getSimpleName 方法获取 ;

// 获取 Activity 的全类名
String className = te.getQualifiedName().toString();

  
 
  • 1
  • 2

根据从 VariableElement 对象中获取的上一级节点的类名 , 对 Set<? extends Element> elements 集合中的 VariableElement 类型元素进行分组 , 同一个 Activity 中对应的 注解节点 对象放在一个 HashSet<VariableElement> 集合中 , 然后放到 HashMap<String, HashSet<VariableElement>> elementMap 中 , 键是 Activity 全类名 ;

具体的集合操作参考下面的源码示例 ;


部分代码示例 :

    /**
     * 搜索 Android 代码中的 BindView 注解
     * 并生成相关代码
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 搜索 BindView , 将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素
        // 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;
        // 通过 getElementsAnnotatedWith 方法可以搜索到整个 Module 中所有使用了 BindView 注解的元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

        // @BindView 注解标注的都是 Activity 中的成员字段,
        // 上述 elements 中的元素都是 VariableElement 类型的节点
        HashMap<String, HashSet<VariableElement>> elementMap = new HashMap<>();

        // 遍历 elements 注解节点 , 为节点分组
        for (Element element : elements){
            // 将注解节点类型强转为 VariableElement 类型
            VariableElement ve = (VariableElement) element;

            // 获取该注解节点对应的成员变量类名
            // 先获取该注解节点的上一级节点 , 注解节点是 VariableElement , 成员字段节点
            // 上一级节点是就是 Activity 类节点对应的 类节点 TypeElement
            TypeElement te = (TypeElement) ve.getEnclosingElement();

            // 获取 Activity 的全类名
            String className = te.getQualifiedName().toString();

            mMessager.printMessage(Diagnostic.Kind.NOTE, "TypeElement : " + className + " , VariableElement : " + ve.getSimpleName());

            // 获取 elementMap 集合中的 Activity 的全类名对应的 VariableElement 节点集合
            // 如果是第一次获取 , 为空 ,
            // 如果之前已经获取了该 Activity 的全类名对应的 VariableElement 节点集合, 那么不为空
            HashSet<VariableElement> variableElements = elementMap.get(className);
            if (variableElements == null){
                variableElements = new HashSet<>();
                // 创建之后 , 将集合插入到 elementMap 集合中
                elementMap.put(className, variableElements);
            }
            // 将本节点插入到 HashSet<VariableElement> variableElements 集合中
            variableElements.add(ve);
        }
        return false;
    }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

编译时 注解处理器 打印的内容 : 在 Build 面板 , " Build Output " 选项卡中输入编译相关信息 , 其中 " Task :app:compileDebugJavaWithJavac " 任务输出 注解处理器 相关日志 ;

在这里插入图片描述





四、注解处理器 完整代码示例



注解处理器 完整代码示例 :

package kim.hsl.annotation_compiler;

import com.google.auto.service.AutoService;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
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.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;

import kim.hsl.annotation.BindView;

/**
 * 生成代码的注解处理器
 */
@AutoService(Processor.class)
public class Compiler extends AbstractProcessor {

    /**
     * 生成 Java 代码对象
     */
    private Filer mFiler;

    /**
     * 日志打印
     */
    private Messager mMessager;

    /**
     * 初始化注解处理器相关工作
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.mFiler = processingEnv.getFiler();
        this.mMessager = processingEnv.getMessager();
    }

    /**
     * 声明 注解处理器 要处理的注解类型
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedAnnotationTypes = new HashSet<String>();
        // 将 BindView 全类名 kim.hsl.annotation.BinndView 放到 Set 集合中
        supportedAnnotationTypes.add(BindView.class.getCanonicalName());
        return supportedAnnotationTypes;
    }

    /**
     * 声明支持的 JDK 版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        // 通过 ProcessingEnvironment 类获取最新的 Java 版本并返回
        return processingEnv.getSourceVersion();
    }

    /**
     * 搜索 Android 代码中的 BindView 注解
     * 并生成相关代码
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 搜索 BindView , 将 BindView 注解放在什么元素上 , 得到的就是相应类型的元素
        // 根据 注解类型 获取 被该注解类型 标注的元素 , 元素可能是类 , 方法 , 字段 ;
        // 通过 getElementsAnnotatedWith 方法可以搜索到整个 Module 中所有使用了 BindView 注解的元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

        // @BindView 注解标注的都是 Activity 中的成员字段,
        // 上述 elements 中的元素都是 VariableElement 类型的节点
        HashMap<String, HashSet<VariableElement>> elementMap = new HashMap<>();

        // 遍历 elements 注解节点 , 为节点分组
        for (Element element : elements){
            // 将注解节点类型强转为 VariableElement 类型
            VariableElement ve = (VariableElement) element;

            // 获取该注解节点对应的成员变量类名
            // 先获取该注解节点的上一级节点 , 注解节点是 VariableElement , 成员字段节点
            // 上一级节点是就是 Activity 类节点对应的 类节点 TypeElement
            TypeElement te = (TypeElement) ve.getEnclosingElement();

            // 获取 Activity 的全类名
            String className = te.getQualifiedName().toString();

            mMessager.printMessage(Diagnostic.Kind.NOTE, "TypeElement : " + className + " , VariableElement : " + ve.getSimpleName());

            // 获取 elementMap 集合中的 Activity 的全类名对应的 VariableElement 节点集合
            // 如果是第一次获取 , 为空 ,
            // 如果之前已经获取了该 Activity 的全类名对应的 VariableElement 节点集合, 那么不为空
            HashSet<VariableElement> variableElements = elementMap.get(className);
            if (variableElements == null){
                variableElements = new HashSet<>();
                // 创建之后 , 将集合插入到 elementMap 集合中
                elementMap.put(className, variableElements);
            }
            // 将本节点插入到 HashSet<VariableElement> variableElements 集合中
            variableElements.add(ve);
        }
        return false;
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124




五、博客资源



博客源码 :

文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。

原文链接:hanshuliang.blog.csdn.net/article/details/117072224

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。