Konlin注解处理器——简易版ButterKnife实现

Kotlin 中实现简易ButterKnife:注解处理器与KotlinPoet实战
本文详述了在Kotlin环境中使用注解处理器(Annotation Processing Tool, APT)和KotlinPoet实现类似ButterKnife的自动绑定View功能。首先介绍了ButterKnife的基本使用,然后讲解了注解处理器的工作原理和配置,包括创建注解、使用APT以及KotlinPoet的使用。接着,通过编写注解处理器的process方法生成Kotlin代码,并在最终的Android应用中使用生成的类。文章还讨论了KotlinPoet生成Kotlin代码的示例,最后展示了如何整合所有组件,实现ButterKnife的自动绑定功能。

1. ButterKnife简介

ButterKnife是一个专注于Android系统的View注入框架,它通过在编译期生成class文件,为开发者自动完成findViewById方法的调用,对注解的View进行实例绑定。
ButterKnife最基本的使用方法分为4步:
1.在build.gradle中添加依赖

	//Java中使用注解处理器不需要添加这个插件
	//kotlin中使用注解处理器需要添加这个插件,否则只能识别java的注解,不能识别kotlin的注解
	//kapt插件能够同时识别kotlin注解和java注解
	apply plugin: 'kotlin-kapt'
	implementation 'com.jakewharton:butterknife:10.1.0'
	//kotlin中,添加注解处理器的依赖写法用annotationProcessor 
	//annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
	//kotlin中,添加注解处理器的依赖写法用kapt
	kapt 'com.jakewharton:butterknife-compiler:10.1.0'

2.对Activity中的View添加@BindView注解。

    @BindView(R.id.tv)
    lateinit var tv: TextView

3.在ActivityonCreate方法中调用setContentView之后,调用ButterKnife.bind(this)对所有的添加了注解的View进行实例绑定。

    override fun onCreate(savedInstanceState: Bundle?) {
   
   
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)
        ButterKnife.bind(this)
    }

4.在Activity中无需调用findViewById方法对View进行赋值,即可直接使用。

    tv.text = "Hello World!!!"

本文要实现的功能就是这样一个最基础的简易版ButterKnife。

2. 正文前的说明

  1. Kotlin提供的kotlin-android-extensions插件已经提供了很方便的View自动绑定功能,所以使用Kotlin时是没必要使用ButterKnife的(个人观点)。
  2. 本文的目的是介绍和记录在kotlin中APT(Annotation Processing Tool,注解处理器)的使用方法,以及如何使用KotlinPoet自动生成kotlin代码,以及在这期间自己踩得一些坑,如果只关心代码实现,可以直接跳转到最后一章,或者查看源代码
  3. 以下内容纯属个人见解结合网上资料完成。存在错误,实属正常,如有不足,欢迎指正。

3. 自动绑定View的原理

实现View的自动绑定需要3个类之间进行合作。

首先,Activity中提供带绑定的View,同时调用ButterKnife.bind(this)完成绑定。代码见ButterKnife简介中的第三代码段

其次,ButterKnife类中提供静态方法bind(activity:Activity)在该方法中通过反射实例化一个Binding类,同时传入activity作为实例化的参数,这个Binding类与具体传入的Activity类相关(即,一个Activity对应一个Binding类,Binding类的命名规则:ButterKnife_**_Binding,**Activity的类名)。具体代码如下:

class ButterKnife {
   
   
    companion object {
   
   
        fun bind(activity: Activity) {
   
   
        	//Binding类的类名由具体的Activity的类名确定
            val clazzName = "${
     
     activity.javaClass.`package`.name}.ButterKnife_${
     
     activity.javaClass.simpleName}_Binding"
            val clazz = Class.forName(clazzName)
            val constructor = clazz.getConstructor(activity.javaClass)
            constructor.newInstance(activity)
        }
    }
}

最后,在Binding类的构造方法中利用activity实例调用findViewById()方法进行View的绑定。具体代码如下:

public class ButterKnife_MainActivity_Binding() {
   
   
  public constructor(activity: MainActivity) : this() {
   
   
    activity.tv=activity.findViewById(2131230814)
  }
}

说明:

  1. 为什么ButterKnife的bind()方法要用反射?因为每个Activity都有自己的Binding类,两者之间只有类名相关,反射调用Binding类的构造方法,在构造方法对View进行赋值,可以为所有的Activity提供统一的绑定View的方式。
  2. 反射不消耗性能么?事实上只是通过反射调用构造方法,并没有反射遍历所有属性并分析注解这种耗时操作,和虚拟机构造一个类的实例差不多。
  3. 每个Activity对应一个Binding类,命名还有要求,写代码不是变复杂了?事实上,Binding类是APT工具在编译期使用KotlinPoet自动生成的。ButterKnife类只有一个,并且写在一个单独的Module里,所以在使用时只需要在Activity中对应的View上打注解,然后调用ButterKnife.bind(this)即可。
  4. @BindView注解的作用是什么?辅助APT生成对用的Binding类。
  5. 其他注意事项:View不能是private,且要声明为lateinit var,不然在Binding类中无法赋值。

4. APT的使用

APT,即注解处理器。在Android中,使用gradle将源文件编译打包成Android的APK文件,事实上是执行了gradle插件中的一个个task,这些task负责完成不同的任务。下图(偷来的,点击查看原文)展示了Android的编译打包流程(缺少签名的过程),APT的工作与图中箭头所指的aapt类似(APT和aapt是两个东西),即APT会在task调用javac对源文件进行编译前被调用,根据APT的代码生成Generated Source Files,生成的代码会和其他的Source Files一起被javac编译成class文件,放进最终的apk中。
在这里插入图片描述
编写APT需要两个要素:注解和注解处理器,使用方法如下。

  1. 创建注解处理器对应的库:New->Module->Java or Kotlin Libray,填写库名和类名->Finish
    注解处理器对应库的创建

    说明:Module一定是Java or Kotlin Libiary,否则注解处理器无法生效。事实上,注解处理器确实不算Android Library,因为它是工作在编译期间的。库名和类名可以随意,但是后面会被用到。请忽略图中的报错,因为不想删库重新创建一遍。

  2. butterknife-annotation-lib/src/main目录下创建文件夹 resources/META-INF/services,并在services文件夹下创建文件javax.annotation.processing.Processor。这一步的每一个文件夹和文件的命名都是固定的,不能修改。最后在javax.annotation.processing.Processor文件中写注解处理器对应类的全限定名。本文中是:com.cam.butterknife_compile_lib.ButterKnifeAnnotationProcessor
    文件的目录结构
    javax.annotation.processing.Processor中的内容

    说明:此步配置是为了让gradle在编译前将注解处理器(ButterKnifeAnnotationProcessor)识别出来并执行其中的代码。

  3. 创建注解类。步骤2类似,创建一个Java or Kotlin Libiary的模块,模块名随意,本文为butterknife-annotation-lib。在模块中创建BindView注解。 butterknife-annotation-lib的文件结构
    BindView注解的代码为:

    @Retention(AnnotationRetention.SOURCE)
    @Target(AnnotationTarget.FIELD)
    annotation class BindView(val value:Int)
    

    说明:

    1. @Retention(AnnotationRetention.SOURCE)说明注解只会存活在源码中,在编译阶段使用。
    2. @Target(AnnotationTarget.FIELD)说明注解使用在属性上的。
    3. value用来记录View的id
    4. @BindView注解放在Java or Kotlin Libiary中,是因为注解处理器后面要读取这个注解,Activity也会使用这个注解,如果是Android的Module,注解处理器的类读取注解时会有问题。也可以将@BindView注解放在和注解处理器同一模块,但是那样Activity所在的模块添加依赖时就会很丑陋。
  4. 添加依赖。
    app模块的build.gradle添加对butterknife-compile-lib模块的依赖(注解处理器模块)、butterknife-annotation-lib模块的依赖(提供注解)、以及声明kapt插件。代码如下:

     	plugins {
         
         
    		id 'com.android.application'
    		id 'kotlin-android'
    		id 'kotlin-kapt'
    	}
    
    	dependencies {
         
         
    		implementation project(path: ':butterknife-annotation-lib')
    		kapt project(path: ':butterknife-compile-lib')
    	}
    

    说明:注解处理器的依赖必须用kapt,不能用annotationProcessor ,否则无法识别打在Kotlin代码上的注解,只能识别打在Java代码上的注解。使用kapt插件,既能识别打在Java上的注解,也能识别打在Kotlin上的注解。

    butterknife-compile-lib模块添加对butterknife-annotation-lib模块的依赖,同时把Java的版本改成Java8(因为后面使用KotlinPoet对Java的版本有要求,不是Java8会报错)。为了方便,添加上KotlinPoet依赖(反正后面迟早要添加),代码如下:

    	java {
         
         
        	sourceCompatibility = JavaVersion.VERSION_1_8
        	targetCompatibility = JavaVersion.VERSION_1_8
    	}
    	dependencies {
         
         
        	implementation project(path: ':butterknife-annotation-lib')
        	implementation "com.squareup:kotlinpoet:1.10.2"
    	}
    

    最后把butterknife-annotation-lib模块中的java版本也改成Java8,不改会怎样呢?没试过!懒得试!修改方法相同,不在赘诉。

  5. 修改注解处理器代码。编写步骤2中创建的ButterKnifeAnnotationProcessor的代码,使其继承AbstractProcessor并重写其中的方法,代码如下。

    <
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值