Android Kotlin Annotation Processer
阅读原文时间:2023年07月08日阅读:1
注解处理器(Annotation Processer)是javac内置的注解处理工具,可以在编译时处理注解,让我们自己做相应的处理.比如生成重复度很高的代码,butterKnife就是借助Annotation Processer来生成findViewById的代码.

本文主要介绍Kotlin环境下如何使用.
Kotlin环境先要使用Annotation Processer需要注意一下几点

a.相关的模块语言必须是kotlin
b.依赖注解处理模块时需要使用kapt来依赖,否则不生效

首先新建一个空白的Android项目,开发语言要选择Kotlin

新建一个annotations模块,选择kotlin,这个模块存放所有的注解.

然后创建一个BindView的注解

  package komine.libs.annotations

  //表示该注解只存在于源码阶段
  @Retention(AnnotationRetention.SOURCE)
  //表示注解可以标记在字段上
  @Target(AnnotationTarget.FIELD)
  annotation class BindView(val viewId:Int = -1)

创建processer模块,用来处理注解

然后在main目录下创建resources\META-INF\services文件夹,并新建javax.annotation.processing.Processor文件

创建一个BindingProcessor类,并继承自AbstractProcessor,重写process方法

  @SupportedSourceVersion(SourceVersion.RELEASE_8)
  class BindingProcessor:AbstractProcessor() {
    //它就表示我们要生成的那个文件
    private lateinit var filer: Filer

    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        filer = processingEnv!!.filer
    }

    override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
        println("annotation process...")
        return true
    }

    //processor模块记得引用annotations模块
    //返回要处理的注解
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return mutableSetOf(BindView::class.java.canonicalName)
    }
}

编辑javax.annotation.processing.Processor文件,将新建的类添加进去,之前的步骤如果没有出现错误的话,IDE应该会有提示

然后在app模块引入kapt插件,如果你的AS版本和我不一致,自己想办法引入kapt

  plugins {
    ...
    ...
    id 'kotlin-kapt'
  }

然后在build.gradle引用processor和annotations模块

    implementation project(path: ':annotations')
    kapt project(path: ':processer')

最后在MainActivity中使用BindView注解

点击rebuild Project,如果配置没有问题,可以在build输出中看到打印的日志

接下来我们来实现butterKnife的findViewById功能

在app项目中新建一个Binding类,也可以放到其他模块

  object Binding {
    fun <T : Any> bind(target: T) {
          try{
              //获取类的名字,之后会通过代码动态生成该类
              //因为只需要反射一次,所以性能几乎没有影响
              val bindingClass = Class.forName(target::class.java.canonicalName + "Binding")
              val constructor = bindingClass.getDeclaredConstructor(target.javaClass)
              constructor.newInstance(target)
          }catch (e:Exception){
              e.printStackTrace()
          }
      }
  }

然后回到processor模块,在process方法中去动态生成绑定类,首先引入implementation 'com.squareup:kotlinpoet:1.12.0'源文件生成工具,

你也可以手动生成.

    override fun process(typeElement: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
        //process方法会多次执行,导致出现Attempt to reopen a file for path错误,添加if判断
        if(typeElement!!.isEmpty()){
            return false
        }

        for (element in roundEnv!!.rootElements){
            //获取包名
            val packageName = element.enclosingElement.toString()
            //获取当前类型的类名
            val classStr = element.simpleName.toString()

            //implementation 'com.squareup:kotlinpoet:1.12.0' class生成工具
            //你也可以按照自己的方式来生成,不用生成工具
            val className = ClassName(packageName,classStr + "Binding")

            val codeBlockBuilder = CodeBlock.Builder()

            //是否需要生成绑定文件
            var needBinding = false
            //遍历类中的所有成员
            for (enclosedElement in element.enclosedElements){
                //如果成员是字段
                if(enclosedElement.kind == ElementKind.FIELD){
                    val bindView = enclosedElement.getAnnotation(BindView::class.java)
                    if(bindView != null && bindView.viewId != -1){
                        needBinding = true
                        //字段上有标注BindView注解
                        //这里的activity表示生成的类的构造函数中的参数名称,你也可以改成其他的,但要保持一致
                        codeBlockBuilder.addStatement("activity.${enclosedElement.simpleName} = activity.findViewById(${bindView.viewId})")
                    }
                }
            }

            if(needBinding){
                //生成Binding源文件
                val file = FileSpec.builder(packageName,className.simpleName)
                    .addType(
                        TypeSpec.classBuilder(className)
                            .primaryConstructor(
                                FunSpec.constructorBuilder()
                                    .addParameter("activity",ClassName(packageName,classStr)).build()
                            )
                            .addInitializerBlock(
                                codeBlockBuilder.build()
                            )
                            .build()
                    )
                file.build().writeTo(filer)
            }
        }

        return false
    }

生成的文件在build/generated/source/kapt文件夹下

然后在MainActivity中绑定

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Binding.bind(this)
        mTextView.text = "Binding"
    }

源码:https://pan.baidu.com/s/19eBpHa-qDMpTEvHRbjq5ww 提取码:3939

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章