【Android】APT与JavaPoet学习与实战

news2024/12/23 8:00:30

PS:本文讲解的APT全称为Annotation Processing Tool,而非是Android Performance Tuner,这两种工具简称皆为APT,前者是“注释处理工具”,后者是“Android性能调试器”。

本文分别使用Javakotlin 语言进行开发,代码已开源 👉 APT-Demo

目录

  • APT
  • 仿ButterKnife组件绑定
    • 创建类
    • 注册类
      • SPI
      • AutoService
  • JavaPoet
  • 最终效果

APT

APT全称为Annotation Processing Tool,是Javac(Java编译器)的一个工具。通过APT,在Javac编译代码时可以通过注解拿到被注解的对象信息,根据需求自动生成模板代码。

PS:APT获取注解和生成代码都是在代码编译时期完成的。

APTJavac 的一个工具,使用的时候在 build.gradle 引入 java-library 插件,使用 java-library 插件的 Module 不应再使用与android相关的插件,避免报The 'java' plugin has been applied, but it is not compatible with the Android plugins.的兼容错误,这也就是为啥建个package就能解决的事情,非得建一个Module的原因。

plugins {
    id 'java-library'
}

仿ButterKnife组件绑定

创建类

  • BindView
    注解类,用于绑定的的xml资源id的存、取。
@Retention(RetentionPolicy.CLASS)
@Target(AnnotationTarget.FIELD)
annotation class BindView(val value: Int)

@Retention(RetentionPolicy.CLASS):表示这个注解保留到编译期
@Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)

  • AutoBind
    获取需要生成类的类名,拼接成使用APT生成的类文件名称,通过反射机制传值给APT生成类的inject方法。
object AutoBind {
    fun inject(target: Any) {
        val className = target.javaClass.canonicalName
        val helperName = "$className$\$Processor"
        try {
            val helper = Class.forName(helperName).getConstructor().newInstance() as IBindHelper
            helper.inject(target)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
  • BindViewProcessor
    用于生成模板类及代码
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.binyouwei.lib_apt.annotation.BindView")
class BindViewProcessor : AbstractProcessor() {
    // 存储绑定的组件信息
    private val mBindActivity: MutableMap<TypeElement, MutableSet<ViewInfo>> = hashMapOf()
    // 处理工具类
    private var mElement: Elements? = null
    // 文件管理工作类
    private var mFiler: Filer? = null
    private var mTypeUtils : Types ? = null
    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        processingEnv?.apply {
            mElement = elementUtils
            mFiler = filer
            mTypeUtils = typeUtils
        }
    }
    override fun process(
        annotations: MutableSet<out TypeElement>?,
        roundEnv: RoundEnvironment?,
    ): Boolean {
        if (annotations != null && annotations.size != 0) {
            val elementsAnnotated = roundEnv?.getElementsAnnotatedWith(BindView::class.java)
            elementsAnnotated?.forEach {
                // VariableElement用于表示字段、枚举常量、方法或构造函数参数、局部变量、资源变量或异常参数。
                val variableElement = it as VariableElement
                // TypeElement表示类或接口程序元素
                val typeElement = variableElement.enclosingElement as TypeElement
                // 将viewInfos实例化
                var viewInfos = mBindActivity[typeElement]
                if (viewInfos == null) {
                    viewInfos = hashSetOf()
                    mBindActivity[typeElement] = viewInfos
                }
                // 拿到注解的值,这里的值是view xml布局的id
                val annotation = variableElement.getAnnotation(BindView::class.java)
                val xmlId = annotation.value
                viewInfos.add(ViewInfo(variableElement.simpleName.toString(), xmlId))
            }
            // 生成不同的Activity类,生成路径为...
            mBindActivity.keys.forEach {
                // 获取要绑定的view名称
                val className = it.simpleName.toString()
                // 获取绑定View所在类的包名
                val packageElement = mElement?.getPackageOf(it) as PackageElement
                val packageName = packageElement.qualifiedName.toString()
                // 生成的类的名字
                val generateCalssName = "$className$\$Processor"
                // 开始编写模板代码
                val code = StringBuilder()
                code.append("package $packageName;\n")
                code.append("import com.binyouwei.lib_apt.template.IBindHelper;\n")
                code.append("public class $generateCalssName implements IBindHelper {\n")
                code.append("\t@Override\n")
                code.append("\tpublic void inject(Object target) {\n")
                code.append("\t\t$className obj = ($className)target;\n")
                mBindActivity[it]?.forEach { viewInfo ->
                    viewInfo.viewName?.run {
                        val initView = "\t\tobj.set${substring(0, 1).uppercase()}${substring(1)}(obj.findViewById(${viewInfo.id}));\n"
                        code.append(initView)
                    }
                }
                code.append("\t}\n")
                code.append("}")
                val createSourceFile = mFiler?.createSourceFile(generateCalssName, it)
                val writer = createSourceFile?.openWriter()
                writer?.apply {
                    write(code.toString())
                    flush()
                    close()
                }
            }
            return true
        }
        return false
    }
    class ViewInfo(var viewName: String, var id: Int)
}
  • IBindHelper
    用于AutoBind类调用inject方法给APT生成的Processor类传递类名。
interface IBindHelper {
    fun inject(target: Any?)
}

注册类

注册自动生成代码的Processor类有两种方式,分别是SPIAutoService

SPI

SPI全称Service Provider Interface,是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。

在这里,使用SPI来启用BindViewProcessor扩展类。由于SPI机制约定通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类,因此需要先创建指定文件夹。

Android StudioTerminal打开项目的模块main文件夹,输入命令行 mkdir -p resources/META-INF/services或手动在项目 main 目录下创建resources/META-INF/services文件夹。

在这里插入图片描述
紧接着创建一个名为javax.annotation.processing.Processor的文件,并在文件里输入继承AbstractProcessor类的类路径(含包名和类名),在我这里填的就是com.binyouwei.lib_apt.processor.BindViewProcessor,文件目录如下:

在这里插入图片描述
完成上述步骤,执行Build菜单栏下的Rebuild Project,就能够在app模块的项目路径\APT\app\build\generated\source\kapt\debug目录下生成一个BindViewProcessor类编写的模板类。

这里再插一张照片。

如果嫌操作步骤麻烦,这一步可以使用Google提供的AutoService的注解代替,更方便快捷。

Android项目中,自定义注解需要引入注解处理器,如果在 Java 文件中使用自定义注解,需要使用 annotationProcessor 引入项目,若在Kotlin 文件中使用自定义注解,则添加 kapt(全称Kotlin Annotation Processing Tool) 插件,并使用 kapt 引入依赖。

apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'

但近期 JetBrains 官方发布的教程显示,kapt已经进入维护模式,且无开发 kapt 相关功能的计划,推荐我们使用 KSP(Kotlin Symbol Processing API),官方理由则是:与 kapt 相比,使用 KSP 的注释处理器的运行速度最多可以提高 2 倍。

SPI 相关文章👉 Java SPI (Service Provider Interface) 机制详解

AutoService

AutoService 是一个 java.util.ServiceLoader 风格服务提供程序的配置/元数据生成器,可用于替换 SPI 来注册类。

代替SPI注册类只有两个步骤:

  1. build.gradle添加依赖包。
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.auto.service:auto-service:1.0-rc7'
    kapt 'com.google.auto.service:auto-service:1.0-rc7'
}
  1. 使用@AutoService(Processor.class)注解绑定 BindViewProcessor
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.apt_annotation.BindView")
class BindViewProcessor : AbstractProcessor() {
	...
}

完成上述步骤并编译即可在项目所在路径\APT\app\build\generated\source\kapt\debug生成一个类,这个类里面生成的代码即模板代码。

感兴趣可参考以下文章👇
1、AutoService介绍
2、AutoService历史版本

PS:如果使用 SPIAutoService 没有正常生成类,都会导致 AutoBind 类报异常。

JavaPoet

JavaPoet 是一个用于生成 .Java 源文件的 Java API

JavaPoet 相关类:

描述
ParameterSpec用于生成参数声明
FieldSpec用于生成的字段声明
AnnotationSpec在声明上生成的注释
JavaFile包含单个顶级类的Java文件
ClassName顶级类和成员类的完全限定类名
MethodSpec用于生成的构造函数或方法声明
TypeSpec用于生成的类、接口或枚举声明
CodeWriterJavaFile 类转换为既适合人工使用又适合javac使用的字符串。
NameAllocator指定Java标识符名称以避免冲突、关键字和无效字符。要使用,请首先创建一个实例并分配所需的所有名称。通常是用户提供的名称和常量的混合。

JavaPoet的使用是蛮简单的,就这几个类,在它的 Github 也举了简单的例子,对着看就能知道自己应该如何根据自己的需求编写代码。

上面我们使用字符串拼接的方式实现类代码的编写,使用 javapoet 进行修改,只需要使用以下代码替换字符串拼接部分即可。

// 编写方法
val method = MethodSpec.methodBuilder("inject")
    .addModifiers(Modifier.PUBLIC)
    .returns(Void.TYPE)
    // 给inject方法添加Override注解
    .addAnnotation(Override::class.java)
    // 给inject方法添加Object类型的target参数
    .addParameter(Object::class.java, "target")
mBindActivity[it]?.forEach { viewInfo ->
    // 循环生成findviewbyId代码
    method.addStatement("$className obj = ($className)target;")
        .addStatement("obj.setText(obj.findViewById(${viewInfo.id}));")
}
// 编写类
val generateCalss = TypeSpec.classBuilder(generateCalssName)
     // 添加对类的修饰
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
     // 为类添加需要实现的接口
    .addSuperinterface(IBindHelper::class.java)
     // 为类添加方法
    .addMethod(method.build())
    .build()
// 新增一个文件并写入类
val javaFile = JavaFile.builder(packageName, generateCalss)
    .build()
javaFile.writeTo(mFiler)

使用 kapt 的编写的代码所生成的代码路径是和 java 版本的是不一样的,本示例代码生成的类路径如下:项目路径APTJava\app\build\generated\ap_generated_sources\debug\out\com\binyouwei\apt_java\MainActivity$$Autobind.java
在这里插入图片描述

更多 JavaPoet 详情见👉 javapoet

最终效果

使用示例:

class MainActivity : AppCompatActivity() {
    @BindView(value = R.id.textView)
    var text: TextView ?=null
    @BindView(value = R.id.Id)
    var Id: TextView ?=null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AutoBind.inject(this)
        text?.text = "APT测试成功"
    }
}

使用 APT 前后:

前往下载 KotlinJava 版代码👉 APT-Demo

参考文档
1、Android APT快速教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/746094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

做一个游戏小项目有多简单?

认识一个朋友&#xff0c;学了很多年的 python, 还停留在 helloworld 阶段&#xff0c;每次拿起又放下&#xff0c;是不是很熟悉&#xff1f;每天都在想&#xff0c;我要学编程&#xff0c;我要学编程&#xff0c;但是又不知道从何学起&#xff0c;学了一点又不知道怎么用&…

java并发编程原理-----线程

目录 上下文切换 java代码创建线程的两种方式 线程的五个状态 线程join方法 多线程之间的影响 上下文切换 CPU的每一个核心同一时刻只能执行一个线程&#xff0c;但是我们会发现电脑同一时刻现实会进行几千个线程&#xff0c;这就是cpu在快速的切换执行线程&#xff0c;由…

Python中的迭代器

一、介绍 在Python中&#xff0c;迭代器是一种访问集合元素的方式&#xff0c;可以用于遍历数据集中的元素&#xff0c;而不需要事先知道集合的大小。迭代器可以被用于循环语句中&#xff0c;例如for循环&#xff0c;来遍历集合中的每个元素。 Python中的迭代器是一个实现了迭…

将Windows系统上的音频、视频通过iTunes传输到iPhone上

这个地方需要下载安装版的iTunes 下载地址&#xff1a; https://www.apple.com/itunes/download/win64 不要从Windows的APP Store中下载iTunes&#xff0c;不好使。 安装完成后&#xff0c;如果是导入一个文件夹中的资料&#xff0c;则点击 【文件】》【将文件夹添加到资料库】…

岛屿数量 (力扣) dfs + bfs (JAVA)

给你一个由 ‘1’&#xff08;陆地&#xff09;和 ‘0’&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设该网格的…

在内卷化竞争时代,金龙鱼重返增长的关键到底是什么?

提到欧丽薇兰、胡姬花、香满园、海皇、金味、丰苑、锐龙洁劲100、丸庄酱油等品牌,多数消费者的第一反应是什么?多数消费者认为是某个不知名的新品牌。问题的重点不在产品&#xff0c;而在主品牌定位。 事实上&#xff0c;这都是金龙鱼母公司益海嘉里旗下的品牌。内行都知道益海…

“坏邻居”导致的kafka生产者TPS下降原因排查

背景&#xff1a; 今天测试了两种不同的场景下kafka producer的tps性能数据&#xff0c;两种场景下都是使用3个线程&#xff0c;每个线程都是对应一个kafka producer&#xff0c;测试发送到kafka集群的消息的量&#xff0c;两个场景的区别是场景A只发送kafka消息&#xff0c;场…

自定义类型

目录 什么是自定义类型 结构体 结构体的声明 常规结构体的声明形式 特殊的结构体声明形式 匿名结构体&#xff1a; 匿名结构体的重命名&#xff1a; 注意事项&#xff1a; 结构体的自引用 什么是结构体的自引用 结构体变量的定义与初始化 方法一&#xff1a; 方法…

总结python安装包(库)过程中的采坑

绝大数的包比如numpy、pandas可以用pip install或者conda install解决&#xff0c;使用pip时可以用pip -V命令看一下自己的pip安装在了哪个虚拟环境&#xff0c;一般pip安装在哪默认就把python包安装在哪。 pip -VC:\Users\20478>pip -V pip 23.1.2 from D:\Python\lib\sit…

Android Java代码与JNI交互 JNI子线程访问Java方法 (八)

🔥 Android Studio 版本 🔥 🔥 创建包含JNI的类 JNIInvokeMethod.java 🔥 package com.cmake.ndk1.jni;import com.cmake.ndk1.base.ICallbackMethod; import com.cmake.ndk1.base.IThreadCallback;public class JNIInvokeMethod {static {System.loadLibrary("…

VBA代码如何切换word和excel(3)

【分享成果&#xff0c;随喜正能量】人不能因为一件好事&#xff0c;高兴一整年&#xff0c;却能因为一个创伤&#xff0c;郁郁终生。痛苦给人的刺激&#xff0c;总是远远大于快乐。成年人的烦恼&#xff0c;和谁说都不合适&#xff0c;悲喜自渡&#xff0c;他人难悟。人最强大…

DDOS防御,阻止DDoS攻击的15个独家技巧

DDoS攻击可以使企业完全宕机数小时以上&#xff0c;而宕机的后果可能很严重&#xff0c;各种规模的企业和政府都可能受到影响。2021年&#xff0c;由于系统中断一小时导致销售额大幅下降&#xff0c;亚马逊为此遭受了约3400万美元的直接财务损失。而随后由于Fakebook的服务中断…

Spring源码系列-第2章-后置工厂处理器和Bean生命周期

第2章-后置工厂处理器和Bean生命周期 后置工厂处理器属于后置处理器&#xff0c;后置处理器是Spring最核心的部分&#xff0c;Spring几乎所有的附加功能全由它完成。 什么是BeanPostProcessor&#xff1f; public interface BeanPostProcessor {/*** Apply this {code BeanPos…

桥接(Bridge)模式

目录 动机使用场景参与者优劣协作实现相关模式应用和思考 桥接模式是将抽象部分和它的实现部分分离&#xff0c;使他们都可以独立的变化的对象结构型模式。桥接模式通过将继承改为组合的方式来解决问题&#xff1b;具体来说就是抽取其中一个维度并使之成为独立的类层次。 动机…

gma 2 教程(二)数据操作:2. 功能逻辑架构和栅格数据类型简介

功能逻辑架构 gma栅格数据操作所含功能/属性的关系结构如下图所示&#xff1a; 栅格数据类型 gma栅格数据类型继承自GDAL&#xff0c;与NumPy数据关联&#xff0c;但又有所不同&#xff0c;详细关系见下表&#xff1a; 栅格格式支持 栅格格式信息统计 gma继承了GDAL全部的栅格…

详谈三次握手

作者&#xff1a;爱塔居 专栏&#xff1a;计算机网络 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步 经过三次的对话&#xff0c;这两个火柴人才确认了双方都能够说话&#xff0c;都能听见。三次握手也是一样的&#xff0c;只要这样才能确认双方的接受与发送能力…

文件操作--按数据块读写文件

函数fread&#xff08;&#xff09;和函数fwrite&#xff08;&#xff09;用于一次读取一组数据&#xff0c;即按数据块读写文件。fread&#xff08;&#xff09;的函数原型为&#xff1a; unsigned int fread(void *buffer ,unsigned int size,unsigned int count ,FILE *fp);…

raid5故障导致LeftHand存储崩溃的服务器数据恢复案例

HP-LeftHand存储简介&#xff1a; HP LeftHand存储支持RAID5、RAID6、RAID10磁盘阵列&#xff0c;支持卷快照&#xff0c;卷动态扩容等。 服务端&#xff1a; 客户端&#xff1a; LeftHand存储分为三个层级&#xff1a;物理磁盘、逻辑磁盘、逻辑卷。多个物理磁盘组成一个逻辑的…

pdf文档加水印怎么弄?用这款软件很方便

在工作中&#xff0c;我们经常需要将PDF文件发送给他人&#xff0c;但无法保证文件内容不被窃取&#xff0c;因此需要添加水印来保证文件的安全性。如果你不知道如何给PDF文件添加水印&#xff0c;以下两款软件可以帮助你轻松实现&#xff0c;一起来看看吧&#xff01; 方法一&…

火爆全网,自动化测试-Allure完美测试报告(详全)卷起来...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、allure简介 A…