【Android APT】注解处理器 ( 根据注解生成 Java 代码 )

举报
韩曙亮 发表于 2022/01/11 00:56:59 2022/01/11
【摘要】 文章目录 一、生成 Java 代码二、实现 IButterKnife 接口三、视图绑定主要操作四、完整注解处理器代码五、博客资源 Android APT 学习进阶路径 : 推荐按照顺序阅...


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


上一篇博客 【Android APT】注解处理器 ( Element 注解节点相关操作 )中 对 注解所标注的 节点 , 进行了获取及分析 , 将 VariableElement 类型的 注解节点 , 按照所在 Activity 进行了分组 ;

本篇博客开发 注解处理器 的 生成代码部分 ;





一、生成 Java 代码



上一篇博客 【Android APT】注解处理器 ( Element 注解节点相关操作 ) 中已经将 注解节点 , 按照 Activity 分组 , 放在了 HashMap<String, ArrayList<VariableElement>> elementMap 数据结构中 , 要生成的 .java 类的个数就是该 HashMap 键值对的个数 ;


目标是生成如下代码 :

package kim.hsl.apt;

import android.view.View;

public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {
    public void bind(kim.hsl.apt.MainActivity target) {
        target.hello = target.findViewById(2131230899);
    }
}

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

逐个遍历 HashMap<String, ArrayList<VariableElement>> elementMap 数据结构 , 要从该 HashMap 中获取上述要生成代码的相关信息 ;


package kim.hsl.apt;

  
 
  • 1

生成上述代码 , 需要获取包名 kim.hsl.apt , 根据 VariableElement 注解节点 , 获取 TypeElement 父节点 , 使用 ElementUtils 获取 TypeElement 节点对应的 PackageElement 包节点 , 调用该节点的 getQualifiedName 方法获取完整的包名信息 ;

//获取对应类的包名
// 获取 VariableElement 的父节点 TypeElement
TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();

// 获取 Activity 名称
String activitySimpleName = typeElement.getSimpleName().toString();

// 获取包节点
PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);

// 获取包名
String packageName = packageElement.getQualifiedName().toString();

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

public class MainActivity_ViewBinder implements IButterKnife<kim.hsl.apt.MainActivity> {

  
 
  • 1

生成上述代码 , 需要获取类名 , 以及完整的包名 和 类名 ; 调用 TypeElement 的 getSimpleName 方法 , 可以获取不带包名的类名 ;

// 获取类名
String className = activitySimpleName + "_ViewBinder";

  
 
  • 1
  • 2

    public void bind(kim.hsl.apt.MainActivity target) {
        target.hello = target.findViewById(2131230899);
    }
}

  
 
  • 1
  • 2
  • 3
  • 4

生成上述代码 , 其中 target.hello = target.findViewById(2131230899); 代码需要循环生成 , 该 Activity 中有多少变量添加了 @BindView 注解 , 就需要有几行上述代码 ;

// public void bind(kim.hsl.apt.MainActivity target){
stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");

for (VariableElement variableElement : variableElements){
    // 循环被注解的字段
    // 为每个 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代码
    // 获取成员变量名
    String variableName = variableElement.getSimpleName().toString();
    // 获取资源 id , 通过注解 , 获取注解属性 value
    int resourceId = variableElement.getAnnotation(BindView.class).value();
    // target.
    stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n");
}

// }
stringBuffer.append("}\n");
// }
stringBuffer.append("}\n");

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




二、实现 IButterKnife 接口



该接口直接定义在主应用 , 上面的 注解处理器 本质上就是在 编译时 生成该接口的实现类 , 并实现了其中的 bind 方法 , 每个 Activity 界面都要 生成一个该接口的子类对象 , 在该 生成的 IButterKnife 子类中进行 组件的 findViewById 的视图绑定操作 ;

package kim.hsl.apt;

public interface IButterKnife<T> {
    void bind(T target);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

严谨一点的话 , 该接口一般是定义在 Android 依赖库 中 ;





三、视图绑定主要操作



在 Activity 界面中 , 调用

ButterKnife.bind(this);

  
 
  • 1

方法 , 即可实现视图绑定操作 , 实际上是通过 Activity 的类名 “MainActivity” , 获取到生成的类名 “MainActivity_ViewBinder” , 通过反射获取该类对象 ;

直接创建该对象 , 并调用对象的 bind 方法 , 即可完成视图绑定 ;


ButterKnife 及静态 bind 方法实现 :

package kim.hsl.apt;

public class ButterKnife {

    /**
     * 在 Activity 中调用该方法, 绑定接口
     * @param target
     */
    public static void bind(Object target){
        String className = target.getClass().getName() + "_ViewBinder";

        try {
            // 通过反射得到 MainActivity_ViewBinder 类对象
            Class<?> clazz = Class.forName(className);

            // 调用生成的代码 MainActivity_ViewBinder 的 bind 方法
            if (IButterKnife.class.isAssignableFrom(clazz)){
                IButterKnife iButterKnife = (IButterKnife) clazz.newInstance();
                iButterKnife.bind(target);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}


  
 
  • 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




四、完整注解处理器代码



package kim.hsl.annotation_compiler;

import com.google.auto.service.AutoService;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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 javax.tools.JavaFileObject;

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, ArrayList<VariableElement>> elementMap = new HashMap<>();

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

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

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

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

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

        // 生成代码
        // 遍历 HashMap<String, HashSet<VariableElement>> elementMap 集合
        // 获取 Key 的迭代器
        Iterator<String> iterator = elementMap.keySet().iterator();

        while (iterator.hasNext()){
            // 获取 Activity 全类名
            String key = iterator.next();

            // 获取 Activity 下被注解标注的 VariableElement 注解节点
            ArrayList<VariableElement> variableElements = elementMap.get(key);

            //获取对应类的包名
            // 获取 VariableElement 的父节点 TypeElement
            TypeElement typeElement = (TypeElement) variableElements.get(0).getEnclosingElement();

            // 获取 Activity 名称
            String activitySimpleName = typeElement.getSimpleName().toString();

            // 获取包节点
            PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(typeElement);
            // 获取包名
            String packageName = packageElement.getQualifiedName().toString();

            // 获取类名
            String className = activitySimpleName + "_ViewBinder";

            // 写出文件的字符流
            Writer writer = null;

            // 获取到包名后 , 开始生成 Java 代码
            try {

                mMessager.printMessage(Diagnostic.Kind.NOTE, "Create Java Class Name : " + packageName + "." + className);

                // 根据 包名.类名_ViewBinder 创建 Java 文件
                JavaFileObject javaFileObject = mFiler.createSourceFile(packageName + "." + className);

                // 生成 Java 代码
                writer = javaFileObject.openWriter();

                // 生成字符串文本缓冲区
                StringBuffer stringBuffer = new StringBuffer();
                // 逐行写入文本到缓冲区中

                // package kim.hsl.apt;
                stringBuffer.append("package " + packageName +";\n");

                // import android.view.View;
                stringBuffer.append("import android.view.View;\n");

                // public class MainActivity_ViewBinding implements IButterKnife<kim.hsl.apt.MainActivity>{
                stringBuffer.append("public class " + className + " implements IButterKnife<" + packageName + "." + activitySimpleName +">{\n");
                //stringBuffer.append("public class " + className +"{\n");

                // public void bind(kim.hsl.apt.MainActivity target){
                stringBuffer.append("public void bind(" + packageName + "." + activitySimpleName + " target){\n");

                for (VariableElement variableElement : variableElements){
                    // 循环被注解的字段
                    // 为每个 VariableElement 注解字段生成 target.findViewById(R.id.xxx); 代码

                    // 获取成员变量名
                    String variableName = variableElement.getSimpleName().toString();
                    // 获取资源 id , 通过注解 , 获取注解属性 value
                    int resourceId = variableElement.getAnnotation(BindView.class).value();

                    // target.
                    stringBuffer.append("target." + variableName + " = target.findViewById(" + resourceId + ");\n");
                }


                // }
                stringBuffer.append("}\n");

                // }
                stringBuffer.append("}\n");

                mMessager.printMessage(Diagnostic.Kind.NOTE, "stringBuffer.toString() : " + stringBuffer.toString());

                mMessager.printMessage(Diagnostic.Kind.NOTE, "writer : " + writer);

                        // 将字符串缓冲区的数据写出到 Java 文件中
                writer.write(stringBuffer.toString());

                mMessager.printMessage(Diagnostic.Kind.NOTE,"write finished");


            } catch (Exception e) {
                mMessager.printMessage(Diagnostic.Kind.NOTE,"IOException");
                e.printStackTrace();
            }finally {
                if (writer != null){
                    try {
                        mMessager.printMessage(Diagnostic.Kind.NOTE,"write closed");
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }

        mMessager.printMessage(Diagnostic.Kind.NOTE,"process finished");

        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
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236




五、博客资源



博客源码 :

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

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

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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