首先看一下一张抽象的Android项目编译流程图
APT(Annotation Processing Tool) 注解处理工具,作为编译中动态处理技术,作用在Android代码编译流程中class字节码被编译转换成dex字节码文件之前的这一环节。
之前有写过Java APT入门的一篇文章,这里介绍Kotlin KAPT,这里暂时不做实战介绍,后续有时间会补上(APT、KAPT、KSP是一个系列的文章)
本文使用KAPT最终生成下列文件代码为示例介绍:
package com.icat.myapt.kotlin
import kotlin.Boolean
import kotlin.Unit
public class MainActivity_BindView {
public final fun bindView(activity: MainActivity): Unit {
activity.testTv = activity.window.decorView.findViewById(2131231134)
}
public companion object {
public const val LOVE: Boolean = true
}
}
大致分为以下步骤
-
新建Android工程
-
新建名为AnnotationLib的JavaLibarry,用于编写需要使用到的注解
-
新建名为AnnotationProcessor的JavaLibrary,用于处理编译过程中的目标注解,生成Kotlin文件
完成以上步骤后的工程结构如下:
三个module的依赖关系如下图:
App依赖AnnotationLib和AnnotationProcessor,AnnotationProcessor依赖AnnotationLib。
在AnnotationProcessor的build.gradle中配置一下依赖资源:
plugins {
id 'java-library'
id 'kotlin'
id 'kotlin-kapt'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
// 依赖注解库
implementation project(path: ':AnnotationLib')
// 用于自动注册注解
implementation 'com.google.auto.service:auto-service:1.0-rc6'
kapt 'com.google.auto.service:auto-service:1.0-rc6'
// 用于生成kotlin文件(1.7.2版本最低支持JDK1.8)
implementation 'com.squareup:kotlinpoet:1.7.2'
}
依赖AnnotationLib后续会引用其中的自定义注解,auto-service自动注册注解的处理框架,kotlinpoet用来调用它的api生成lotlin代码
在AnnotationLib中新建ClassPath和ViewId编译时注解
@Target(AnnotationTarget.CLASS)
// 编译时注解,在反射时不可见
@Retention(AnnotationRetention.BINARY)
annotation class ClassPath()
@Target(AnnotationTarget.FIELD)
// 编译时注解,在反射时不可见
@Retention(AnnotationRetention.BINARY)
annotation class ViewId(val viewId: Int)
在App的MainActivity中添加ClassPath和ViewId注解
@ClassPath
class MainActivity : AppCompatActivity() {
@AutoWire
@ViewId(R.id.testTv)
var testTv: TextView? = null
然后在AnnotationProcessor自定义一个FindViewProcessor重写以下方法
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class FindViewProcessor : AbstractProcessor() {
/**
* 用于操作元素的实用方法的实现
*/
private lateinit var mElementUtil: Elements
/**
* 用于对类型进行操作的实用程序方法的实现
*/
private lateinit var mTypeUtil: Types
/**
* 添加支持的注解,在编译过程中会对这些注解进行过滤
*/
override fun getSupportedAnnotationTypes(): MutableSet {
val supportedAnnotationTypes = mutableSetOf()
// 添加支持的注解
supportedAnnotationTypes.add(ClassPath::class.java.canonicalName)
supportedAnnotationTypes.add(ViewId::class.java.canonicalName)
return supportedAnnotationTypes
}
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
println("==【Kt】=========== 【进入init方法】 =============")
processingEnv?.let {
mElementUtil = it.elementUtils
mTypeUtil = it.typeUtils
}
}
override fun process(annotations: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean {
println("==【Kt】=========== 【进入process方法】 =============")
return false
}
然后编译(Build--->ReBuild),控制台切换到Build,观察Builder打印的信息,如果FindViewProcessor被编译到了就会打印
下边编写生成目标文件的代码(MainActivity_BindView),主要分为一下6步:
- Step1: 根据指定注解找到被注解标记的类
- Step2: 遍历每一个被注解标记的类
- Step3: 构造类里边的方法
- Step4: 构造类
- Step5: 构造文件File
- Step6: 写入文件
下边列出主要的语法
生成属性
PropertySpec.builder("变量名称","类型")
.mutable("是否是可变变量")
.initializer("初始化值")
.addModifiers("添加限定符")
.build()
生成方法
FunSpec.builder("方法名称")
.addParameter("添加方法参数")
.addStatement("添加方法体语句")
.addAnnotation("添加注解")
.addModifiers("添加方法限制符,例如private、final、const...")
.returns("方法返回类型")
.build()
生成companion object代码块(注意:一个类里边只能有一个静态代码块)
TypeSpec.companionObjectBuilder()
.addProperties("静态变量集合")
.addFunction("往companion object中添加方法")
.build()
生成类
TypeSpec.classBuilder("类名")
.addType("添加类成员:方法、内部类、接口、属性、对象等")
.addFunction("添加方法")
.build()
生成文件
FileSpec.builder("包名","文件名")
.addType("文件里边的内容对象:方法、类、接口、变量、常量等")
.build()
最后写入文件
private fun FileSpec.writeFile() {
val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]
kaptKotlinGeneratedDir?.let {
val outputFile = File(it).apply {
mkdirs()
}
println("====> 文件数量 -> 路径:${outputFile.toPath()} it:$it")
writeTo(outputFile.toPath())
}
}
可以针对属性变量、方法、类、静态代码块、接口、文件等做一些封装,下边贴出FindViewProcessor:
package com.icat.annoteprocessor.processors
import com.google.auto.service.AutoService
import com.icat.annotationlib.annotations.*
import com.icat.annoteprocessor.Logger
import com.icat.annoteprocessor.sentence.*
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.jvm.jvmDefault
import org.checkerframework.checker.units.qual.A
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import java.util.*
import javax.lang.model.element.*
import javax.lang.model.element.TypeElement
import java.io.File
/**
* @author: PengQun
* @since: 12/22/21
* @Description: 过滤生成findView文件的注解处理器
*/
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class FindViewProcessor : AbstractProcessor() {
private lateinit var mLogger: Logger
/**
* 用于操作元素的实用方法的实现
*/
private lateinit var mElementUtil: Elements
/**
* 用于对类型进行操作的实用程序方法的实现
*/
private lateinit var mTypeUtil: Types
/**
* 用于创建新的源,类或辅助文件的文件管理器
*/
private lateinit var mFiler: Filer
/**
* 用于生成方法
*/
private lateinit var mFunSpecUtil: SentenceFunSpecUtil
/**
* 用于生成属性
*/
private lateinit var mPropertiesSpecUtil: SentencePropertiesUtil
/**
* 用于生成类
*/
private lateinit var mClassSpecUtil: SentenceClassSpecUtil
/**
* 用于缓存TypeSpec
*/
private val typeSpecSet = mutableSetOf()
/**
* 用于缓存FunSpec
*/
private val funSpecSet = mutableSetOf()
/**
* 用于缓存propertySpec
*/
private val propertySpecSet = mutableSetOf()
/**
* 用于缓存静态propertySpec
*/
private val propertySpecCompanionSet = mutableSetOf()
/**
* 用于缓存FileSpec
*/
private val fileSpecSet = mutableSetOf()
/**
* 添加支持的注解,在编译过程中会对这些注解进行过滤
*/
override fun getSupportedAnnotationTypes(): MutableSet {
val supportedAnnotationTypes = mutableSetOf()
// 添加支持的注解
supportedAnnotationTypes.add(ClassPath::class.java.canonicalName)
supportedAnnotationTypes.add(ViewId::class.java.canonicalName)
return supportedAnnotationTypes
}
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
println("==【Kt】=========== 【进入init方法】 =============")
processingEnv?.let {
mLogger = Logger(it.messager)
mElementUtil = it.elementUtils
mTypeUtil = it.typeUtils
mFiler = it.filer
mFunSpecUtil = SentenceFunSpecUtil()
mPropertiesSpecUtil = SentencePropertiesUtil()
mClassSpecUtil = SentenceClassSpecUtil()
}
}
override fun process(annotations: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean {
println("==【Kt】=========== 【进入process方法】 =============")
annotations?.let {
annotations.forEach { typeElement ->
val qualifiedName = typeElement.qualifiedName.toString()
val canonicalName = ClassPath::class.java.canonicalName
// 过滤找到目标的注解
if (qualifiedName == canonicalName) {
println("==【Kt】===========即将开始生成文件")
// 根据类上标记的注解生成字节码文件
generateFile(typeElement, roundEnvironment)
}
}
}
return false
}
/**
* 生成findView的文件,后边用于反射赋值
*
* @param typeElement 注解元素
* @param roundEnvironment 当前环境
*/
private fun generateFile(typeElement: TypeElement, roundEnvironment: RoundEnvironment?) {
if (roundEnvironment == null) {
println("==【Kt】===========roundEnvironment is Null")
return
}
// Step1: 根据指定注解找到被注解标记的类
val elementsAnnotatedWithClass = roundEnvironment.getElementsAnnotatedWith(typeElement)
// Step2: 遍历每一个被注解标记的类
elementsAnnotatedWithClass.forEach continuing@{
if (it is TypeElement) {
val simpleName = it.simpleName.toString()
println("====【Kt】===== simpleName:$simpleName")
// Step3: 构造类里边的方法
val findViewFun = generateFindViewFun(it)
// Step4: 构造类
val findViewTypeSpec = generateFindViewTypeSpec(it, findViewFun)
// Step5: 构造文件File
val fileSpec = generateFileSpec(it, findViewTypeSpec)
// Step6: 写入文件
fileSpec.writeFile()
}
}
}
/**
* 使用kotlinpoet构建方法
*/
private fun generateFindViewFun(elementsAnnotatedClass: TypeElement): FunSpec {
// kotlinpoet构造方法
// val funSpec = FunSpec.builder("bindView")
// .addModifiers(KModifier.PUBLIC, KModifier.FINAL)
// .addParameter(ParameterSpec("activity", getClassNameSimpleName(elementsAnnotatedClass)))
// .returns(UNIT)
val kModifiers = setOf(KModifier.PUBLIC, KModifier.FINAL)
val parameterSpec = ParameterSpec("activity", getClassName(elementsAnnotatedClass))
val funSpecBuilder = mFunSpecUtil.generateCompanionPublicBuilderFun("bindView", modifiers = kModifiers, parameterSpec = parameterSpec)
// 获取该类中需要赋值的成员
val elementList = mElementUtil.getAllMembers(elementsAnnotatedClass)
elementList.forEach continuing@{ classMember ->
// 从成员上获取指定的注解 (findViewById)
// val viewIdAnnotation = classMember.getAnnotation(ViewId::class.java) /*?: return@continuing*/
// if (viewIdAnnotation != null) {
// funSpecBuilder.addStatement(findViewByIdStatement(classMember, viewIdAnnotation))
// }
addStatementByAnnotationType(classMember, ViewId::class.java, funSpecBuilder)
}
return funSpecBuilder.build()
}
// TODO: 12/24/21 工厂模式 -》 返回语句
private fun addStatementByAnnotationType(classMember: Element, typeClass: Class, funSpecBuilder: FunSpec.Builder) {
val annotation = classMember.getAnnotation(typeClass)
if (annotation != null) {
when (typeClass) {
ViewId::class.java -> {
println("====> ViewId::class.java")
funSpecBuilder.addStatement(findViewByIdStatement(classMember, (annotation as ViewId)))
}
AutoWire::class.java -> {
println("====> AutoWire::class.java")
}
}
}
}
/**
* findViewById的语句
*/
private fun findViewByIdStatement(classMember: Element, annotation: ViewId): String {
return String.format(
Locale.CHINA,
"activity.%s = activity.window.decorView.findViewById<%s>(%s)",
classMember.simpleName,
classMember.asType().toString(),
annotation.viewId
)
}
/**
* 使用kotlinpoet构建生成类
*/
fun generateFindViewTypeSpec(typeElement: TypeElement, funSpec: FunSpec): TypeSpec {
// 构建属性
// val propertiesSpec1 = mPropertiesSpecUtil.generatePropertiesSpec("NAME", STRING, false, """"Oracle version 1.8.12"""", KModifier.PUBLIC)
// val propertiesNameSpec = generateCompanionProperties("NAME", STRING, KModifier.PRIVATE)
// propertiesSpecSet.add(propertiesSpec1)
//
// val propertiesAgeSpec = generateCompanionProperties("AGE", INT, KModifier.CONST)
// propertiesSpecSet.add(propertiesAgeSpec)
// 静态变量
val propertyCompanionSpec1 = mPropertiesSpecUtil.generatePropertySpec(
"LOVE",
BOOLEAN,
mutable = false,
initialFormat = String.format("%b", true),
modifier = KModifier.CONST
)
propertySpecCompanionSet.add(propertyCompanionSpec1)
// 构建生成静态代码块 伴生对象只能存在一个
val companionTypeSpec = generateCompanionFun(funSpec)
println("====》 数量:${typeSpecSet.size}")
// 类变量
// 生成可读可写类变量1
// val propertyClassSpec1 = mPropertiesSpecUtil.generatePropertySpec("name", STRING, mutable = false, """"马小跳"""")
// 生成可读不可写类变量2
// val propertyClassSpec2 = mPropertiesSpecUtil.generatePropertySpec("address", STRING, initialFormat = """""上海市马家庄1号"""")
// kotlinpoet构造类
val className = getClassName(typeElement).simpleName + "_BindView"
return TypeSpec.classBuilder(className)
.addType(companionTypeSpec)
// 往类里边添加方法
.addFunction(funSpec)
.build()
// return mClassSpecUtil.generateClassSpec(className, typeSpecSet = typeSpecSet/*, typeSpec = companionTypeSpec*/)
// return TypeSpec.classBuilder(getClassNameSimpleName(typeElement) + "_BindView")
// .addProperties(propertiesSpecSet)
// .addTypes(typeSpecSet)
// .addModifiers(KModifier.PUBLIC, KModifier.FINAL)
// .addFunction(funSpecModel)
// .addFunctions(funSet)
// .build()
}
/**
* 新建一个简单的类:Hello World
*/
private fun helloWorld(): TypeSpec {
val android = PropertySpec.builder("androidVersion", STRING)
.addModifiers(KModifier.PRIVATE)
.initializer("""Android S""")
.build()
return TypeSpec.classBuilder("HelloWorld")
.addProperty(android)
.addProperty("NDK_VERSION", STRING, KModifier.PRIVATE)
.build()
}
/**
* 构建静态代码块
*/
private fun generateCompanionFun(funSpecModel: FunSpec): TypeSpec {
return TypeSpec.companionObjectBuilder()
.addProperties(propertySpecCompanionSet)
// 往companion object中添加方法
// .addFunction(funSpecModel)
.build()
}
/**
* 构建生成静态属性
*/
private fun generateCompanionProperties(propertiesName: String, typeName: TypeName, modifiers: KModifier): PropertySpec {
return PropertySpec.builder(propertiesName, typeName, modifiers)
.mutable()
.initializer(""""Oracle version 1.8.12"""")
.build()
}
/**
* 将构建的类model写入,生成实体文件
*/
private fun generateFileSpec(typeElement: TypeElement, typeSpec: TypeSpec): FileSpec {
val packageName = getPackageName(typeElement)
val fileName = getClassName(typeElement).simpleName + "_BindView"
return FileSpec.builder(packageName, fileName)
.addType(typeSpec)
.build()
}
/**
* 在IO Thread写入文件
*/
private fun FileSpec.writeFile() {
val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]
kaptKotlinGeneratedDir?.let {
val outputFile = File(it).apply {
mkdirs()
}
println("====> 文件数量 -> 路径:${outputFile.toPath()} it:$it")
writeTo(outputFile.toPath())
}
}
/**
* 获取包名
* @param element typeElement
* @return 返回包名
*/
private fun getPackageName(element: Element): String {
// Qualified 获取全路径名称
println("包名--> ${mElementUtil.getPackageOf(element).qualifiedName}")
return mElementUtil.getPackageOf(element).qualifiedName.toString()
}
/**
* 获取ClassName的
* @return ClassName()
*/
private fun getClassName(element: Element): ClassName {
val packageName = getPackageName(element)
val simpleName = element.simpleName.toString()
return ClassName(packageName, simpleName)
}
}
最后经过编译之后就会生成文章开头的目标文件。这片入门文章到此完毕,EventBus、ARouter等框架都是用了APT,实战内容后边会介绍、下一片介绍KSP。
这里推荐一篇介绍KotlinPoet语法的文章