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

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

文章目录

一、生成 Java 代码

二、实现 IButterKnife 接口

三、视图绑定主要操作

四、完整注解处理器代码

五、博客资源


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


【Java 注解】注解简介及作用

【Java 注解】自定义注解 ( 注解属性定义与赋值 )

【Java 注解】自定义注解 ( 元注解 )

【Java 注解】自定义注解 ( 注解解析 )

【Java 注解】自定义注解 ( 使用注解实现简单测试框架 )

【Android APT】编译时技术 ( ButterKnife 原理分析 )

【Android APT】编译时技术 ( 编译时注解 和 注解处理器 依赖库 )

【Android APT】编译时技术 ( 开发编译时注解 )

【Android APT】注解处理器 ( 注解标注 与 初始化方法 )

【Android APT】注解处理器 ( 配置注解依赖、支持的注解类型、Java 版本支持 )

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

【Android APT】注解处理器 ( 生成代码并自动绑定控件 )


上一篇博客 【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);
    }
}


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



package kim.hsl.apt;

生成上述代码 , 需要获取包名 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();



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


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


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



 

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


生成上述代码 , 其中 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");




二、实现 IButterKnife 接口


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


package kim.hsl.apt;
public interface IButterKnife<T> {
    void bind(T target);
}


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






三、视图绑定主要操作


在 Activity 界面中 , 调用


ButterKnife.bind(this);


方法 , 即可实现视图绑定操作 , 实际上是通过 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();
        }
    }
}







四、完整注解处理器代码


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;
    }
}





五、博客资源


博客源码 :


GitHub : https://github.com/han1202012/APT


CSDN : https://download.csdn.net/download/han1202012/18917831


目录
相关文章
|
10月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
207 7
|
7月前
|
Java 编译器 开发者
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
155 25
|
12月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
252 43
Java学习十六—掌握注解:让编程更简单
|
10月前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
362 5
|
11月前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
113 0
|
12月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
89 0
|
XML Java 数据格式
Java-spring注解的作用
Java-spring注解的作用
102 0
|
Android开发
【Android 组件化】路由组件 ( 注解处理器调试 )(二)
【Android 组件化】路由组件 ( 注解处理器调试 )(二)
97 0
【Android 组件化】路由组件 ( 注解处理器调试 )(二)
|
27天前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
285 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
1月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
123 6

热门文章

最新文章

下一篇
oss教程