APT入门

首先看一下一张抽象的Android项目编译流程图


Android工程编译流程.png

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<android.widget.TextView>(2131231134)
  }

  public companion object {
    public const val LOVE: Boolean = true
  }
}
大致分为以下步骤
  • 新建Android工程
  • 新建名为AnnotationLib的JavaLibarry,用于编写需要使用到的注解
  • 新建名为AnnotationProcessor的JavaLibrary,用于处理编译过程中的目标注解,生成Kotlin文件

完成以上步骤后的工程结构如下:


image.png

三个module的依赖关系如下图:


image.png

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<String> {
        val supportedAnnotationTypes = mutableSetOf<String>()
        // 添加支持的注解
        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<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
        println("==【Kt】=========== 【进入process方法】 =============")
  
        return false
    }

然后编译(Build--->ReBuild),控制台切换到Build,观察Builder打印的信息,如果FindViewProcessor被编译到了就会打印


image.png

下边编写生成目标文件的代码(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<TypeSpec>()
    /**
     * 用于缓存FunSpec
     */
    private val funSpecSet = mutableSetOf<FunSpec>()
    /**
     * 用于缓存propertySpec
     */
    private val propertySpecSet = mutableSetOf<PropertySpec>()
    /**
     * 用于缓存静态propertySpec
     */
    private val propertySpecCompanionSet = mutableSetOf<PropertySpec>()
    /**
     * 用于缓存FileSpec
     */
    private val fileSpecSet = mutableSetOf<FileSpec>()

    /**
     * 添加支持的注解,在编译过程中会对这些注解进行过滤
     */
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        val supportedAnnotationTypes = mutableSetOf<String>()
        // 添加支持的注解
        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<out TypeElement>?, 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 <A : Annotation> addStatementByAnnotationType(classMember: Element, typeClass: Class<A>, 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语法的文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容