Kotlin编译时注解,简单实现ButterKnife
ButterKnife在之前的Android开发中还是比较热门的工具,帮助Android开发者减少代码编写,而且看起来更加的舒适,于是简单实现一下ButterKnife,相信把下面的代码都搞懂,看ButterKnife的难度就小很多。
今天实现的是编译时注解,其实运行时注解也一样能实现ButterKnife的效果,但是相对于编译时注解,运行时注解会更耗性能一些,主要是由于运行时注解大量使用反射。
一、创建java library(lib_annotations)
我这里创建3个annotation放在3个文件中
- //绑定layout
@Target(AnnotationTarget.CLASS)- @Retention(AnnotationRetention.BINARY)
- annotation class BindLayout(val value: Int = -1)
//绑定view- @Target(AnnotationTarget.FIELD)
- @Retention(AnnotationRetention.RUNTIME)
- annotation class BindView (val value:Int = -1)
//点击注解- @Target(AnnotationTarget.FUNCTION)
- @Retention(AnnotationRetention.BINARY)
- annotation class OnClick (vararg val values:Int)
Kotlin对编译时注解时Retention 并没有太多的要求,一般我们使用AnnotationRetention.BINARY或者SOURCE,但是我发现ButterKnife用的是Runtime,测试也可以。
但具体为什么用,不是特别明白,自己认为是AnnotationRetention.RUNTIME基本包含了BINARY或者SOURCE的功能,还支持反射。
二、创建java library(lib_processor)
- @AutoService(Processor::class)
- @SupportedSourceVersion(SourceVersion.RELEASE_8)
- class BindProcessor : AbstractProcessor() {
- companion object {
- private const val PICK_END = "_BindTest"
- }
- private lateinit var mLogger: Logger
- //存储类文件数据
- private val mInjectMaps = hashMapOf<String, InjectInfo>()
//必须实现方法- override fun process(
- annotations: MutableSet<out TypeElement>?,
- roundEnv: RoundEnvironment
- ): Boolean {
- //里面就要生成我们需要的文件
- roundEnv.getElementsAnnotatedWith(BindLayout::class.java).forEach {
- bindLayout(it)
- }
- roundEnv.getElementsAnnotatedWith(BindView::class.java).forEach {
- bindView(it)
- }
- roundEnv.getElementsAnnotatedWith(OnClick::class.java).forEach {
- bindClickListener(it)
- }
- mInjectMaps.forEach { (name, info) ->
- //这里生成文件
- val file= FileSpec.builder(info.packageName, info.className.simpleName + PICK_END)
- .addType(
- TypeSpec.classBuilder(info.className.simpleName + PICK_END)
- .primaryConstructor(info.generateConstructor()).build()
- ).build()
- file.writeFile()
- }
- return true
- }
- private fun FileSpec.writeFile() {
- //文件编译后位置
- val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]
- val outputFile = File(kaptKotlinGeneratedDir).apply {
- mkdirs()
- }
- writeTo(outputFile.toPath())
- }
- private fun bindLayout(element: Element) {
- //BindLayout注解的是Class,本身就是TypeElement
- val typeElement = element as TypeElement
- //一个类一个injectInfo
- val className = typeElement.qualifiedName.toString()
- var injectInfo = mInjectMaps[className]
- if (injectInfo == null) {
- injectInfo = InjectInfo(typeElement)
- }
- typeElement.getAnnotation(BindLayout::class.java).run {
- injectInfo.layoutId = value
- }
- mInjectMaps[className] = injectInfo
- }
- private fun bindView(element: Element) {
- //BindView注解的是变量,element就是VariableElement
- val variableElement = element as VariableElement
- val typeElement = element.enclosingElement as TypeElement
- //一个类一个injectInfo
- val className = typeElement.qualifiedName.toString()
- var injectInfo = mInjectMaps[className]
- if (injectInfo == null) {
- injectInfo = InjectInfo(typeElement)
- }
- variableElement.getAnnotation(BindView::class.java).run {
- injectInfo.viewMap[value] = variableElement
- }
- mInjectMaps[className] = injectInfo
- }
- private fun bindClickListener(element: Element) {
- //OnClick注解的是方法,element就是VariableElement
- val variableElement = element as ExecutableElement
- val typeElement = element.enclosingElement as TypeElement
- //一个类一个injectInfo
- val className = typeElement.qualifiedName.toString()
- var injectInfo = mInjectMaps[className]
- if (injectInfo == null) {
- injectInfo = InjectInfo(typeElement)
- }
- variableElement.getAnnotation(OnClick::class.java).run {
- values.forEach {
- injectInfo.clickListenerMap[it] = variableElement
- }
- }
- mInjectMaps[className] = injectInfo
- }
//把注解类都添加进行,这个方法一看方法名就应该知道干啥的- override fun getSupportedAnnotationTypes(): Set<String> {
- return setOf(
- BindLayout::class.java.canonicalName,
- BindView::class.java.canonicalName,
- OnClick::class.java.canonicalName
- )
- }
- override fun init(processingEnv: ProcessingEnvironment) {
- super.init(processingEnv)
- mLogger = Logger(processingEnv.messager)
- mLogger.info("processor init")
- }
- }
- //存储一个Activity文件所有注解数据,并有相应方法生成编译后的文件
class InjectInfo(val element: TypeElement) {- var mLogger: Logger? = null
- //类名
- val className: ClassName = element.asClassName()
- val viewClass: ClassName = ClassName("android.view", "View")
- //包名
- val packageName: String = getPackageName(element).qualifiedName.toString()
- //布局只有一个id
- var layoutId: Int = -1
- //View 注解数据可能有多个 注意是VariableElement
- val viewMap = hashMapOf<Int, VariableElement>()
- //点击事件 注解数据可能有多个 注意是ExecutableElement
- val clickListenerMap = hashMapOf<Int, ExecutableElement>()
- private fun getPackageName(element: Element): PackageElement {
- var e = element
- while (e.kind != ElementKind.PACKAGE) {
- e = e.enclosingElement
- }
- return e as PackageElement
- }
- fun getClassName(element: Element): ClassName {
- var elementType = element.asType().asTypeName()
- return elementType as ClassName
- }
//自动生成构造方法,主要使用kotlinpoet- fun generateConstructor(): FunSpec {
//构造方法,传入activity参数- val builder = FunSpec.constructorBuilder().addParameter("target", className)
- .addParameter("view", viewClass)
- if (layoutId != -1) {
- builder.addStatement("target.setContentView(%L)", layoutId)
- }
- viewMap.forEach { (id, variableElement) ->
- builder.addStatement(
- "target.%N = view.findViewById(%L)",
- variableElement.simpleName,
- id
- )
- }
- clickListenerMap.forEach { (id, element) ->
- when (element.parameters.size) {
- //没有参数
- 0 -> builder.addStatement(
- "(view.findViewById(%L) as View).setOnClickListener{target.%N()}"
- , id
- )
- //一个参数
- 1 -> {
- if (getClassName(element.parameters[0]) != viewClass) {
- mLogger?.error("element.simpleName function parameter error")
- }
- builder.addStatement(
- "(view.findViewById(%L) as View).setOnClickListener{target.%N(it)}"
- , id, element.simpleName
- )
- }
- //多个参数错误
- else -> mLogger?.error("element.simpleName function parameter error")
- }
- }
- return builder.build()
- }
- }
三、app module中引入上面两个lib
- //gradle引入
implementation project(':lib_annotations')- kapt project(':lib_processor')
- @BindLayout(R.layout.activity_main)
- class MainActivity : AppCompatActivity() {
- @BindView(R.id.tv_hello)
- lateinit var textView: TextView
- @BindView(R.id.bt_click)
- lateinit var btClick: Button
- private var mClickBtNum = 0
- private var mClickTvNum = 0
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- // setContentView(R.layout.activity_main)
- //这里第4步内容
- BindApi.bind(this)
- textView.text = "测试成功......"
- btClick.text = "点击0次"
- }
- @OnClick(R.id.bt_click, R.id.tv_hello)
- fun onClick(view: View) {
- when (view.id) {
- R.id.bt_click -> {
- mClickBtNum++
- btClick.text = "点击${mClickBtNum}次"
- }
- R.id.tv_hello -> {
- mClickTvNum++
- textView.text = "点击文字${mClickTvNum}次"
- }
- }
- }
- }
现在就可以直接编译,编译后我们就可以找到编译生成的类MainActivity_BindTest,
- import android.view.View
- class MainActivity_BindTest(
- target: MainActivity,
- view: View) {
- init {
- target.setContentView(2131361820)
- target.btClick = view.findViewById(2131165250)
- target.textView = view.findViewById(2131165360)
- (view.findViewById(2131165250) as View).setOnClickListener { target.onClick(it) }
- (view.findViewById(2131165360) as View).setOnClickListener { target.onClick(it) }
- }
- }
这里当然还不能用,因为我们没有把MainActivity_BindTest和MainActivity关联上。
四、创建App module(lib_api)
- object BindApi {
- //类似ButterKnife方法
- fun bind(target: Activity) {
- val sourceView = target.window.decorView
- createBinding(target, sourceView)
- }
- private fun createBinding(target: Activity, source: View) {
- val targetClass = target::class.java
- var className = targetClass.name
- try {
- //获取类名
- val bindingClass = targetClass.classLoader!!.loadClass(className + "_BindTest")
- //获取构造方法
- val constructor = bindingClass.getConstructor(targetClass, View::class.java)
- //向方法中传入数据activity和view
- constructor.newInstance(target, source)
- } catch (e: ClassNotFoundException) {
- e.printStackTrace()
- } catch (e: NoSuchMethodException) {
- e.printStackTrace()
- } catch (e: IllegalAccessException) {
- e.printStackTrace()
- } catch (e: InstantiationException) {
- e.printStackTrace()
- } catch (e: InvocationTargetException) {
- e.printStackTrace()
- }
- }
- }
并在app中引用
- implementation project(':lib_api')
五、总结
流程还是比较简单,创建annotation、processor、lib_api 3个module,我们打包时并不需要processor包,它的目的仅仅是生成相应的文件代码。
注意点:
1、annotation 和processor要引入
- apply plugin: 'kotlin'
2、编译时打印使用Messager,注意JDK8打印NOTE无法显示
3、lib_api 文件在反射时要注意和processor对应,修改时注意同步修改等
有用的话加个关注哦!!!
Kotlin编译时注解,简单实现ButterKnife的更多相关文章
- 使用编译时注解简单实现类似 ButterKnife 的效果
这篇文章是学习鸿洋前辈的 Android 如何编写基于编译时注解的项目 的笔记,用于记录我的学习收获. 读完本文你将了解: 什么是编译时注解 APT 编译时注解如何使用与编写 举个例子 思路 创建注解 ...
- Android 编译时注解解析框架
2.注解 说道注解,竟然还有各种分类,得,这记不住,我们从注解的作用来反推其分类,帮助大家记忆,然后举例强化大家的记忆,话说注解的作用: 1.标记一些信息,这么说可能太抽象,那么我说,你见过@Over ...
- Android 打造编译时注解解析框架 这只是一个开始
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43452969 ,本文出自:[张鸿洋的博客] 1.概述 记得很久以前,写过几篇博客 ...
- 利用APT实现Android编译时注解
摘要: 一.APT概述 我们在前面的java注解详解一文中已经讲过,可以在运行时利用反射机制运行处理注解.其实,我们还可以在编译时处理注解,这就是不得不说官方为我们提供的注解处理工具APT (Anno ...
- java 编译时注解框架 lombok-ex
lombok-ex lombok-ex 是一款类似于 lombok 的编译时注解框架. 编译时注,拥有运行时注解的便利性,和无任何损失的性能. 主要补充一些 lombok 没有实现,且自己会用到的常见 ...
- lombok编译时注解@Slf4j的使用及相关依赖包
slf4j是一个日志门面模式的框架,只对调用者开放少量接口用于记录日志 主要接口方法有 debug warn info error trace 在idea中可以引入lombok框架,使用@Slf4j注 ...
- Java 进阶巩固:什么是注解以及运行时注解的使用
这篇文章 2016年12月13日星期二 就写完了,当时想着等写完另外一篇关于自定义注解的一起发.结果没想到这一等就是半年多 - -. 有时候的确是这样啊,总想着等条件更好了再干,等准备完全了再开始,结 ...
- apt 根据注解,编译时生成代码
apt: @Retention后面的值,设置的为CLASS,说明就是编译时动态处理的.一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~ ...
- 自定义注解之运行时注解(RetentionPolicy.RUNTIME)
对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas ...
随机推荐
- 牛客-2018多校算法第五场D-集合问题+并查集
集合问题 题意: 给你a,b和n个数p[i],问你如何分配这n个数给A,B集合,并且满足: 若x在集合A中,则a-x必须也在集合A中. 若x在集合B中,则b-x必须也在集合B中. 思路:并查集操作,自 ...
- Aizu-2249 Road Construction(dijkstra求最短路)
Aizu - 2249 题意:国王本来有一个铺路计划,后来发现太贵了,决定删除计划中的某些边,但是有2个原则,1:所有的城市必须能达到. 2:城市与首都(1号城市)之间的最小距离不能变大. 并且在这2 ...
- CodeForces 1082 G Petya and Graph 最大权闭合子图。
题目传送门 题意:现在有一个图,选择一条边,会把边的2个顶点也选起来,最后会的到一个边的集合 和一个点的集合 , 求边的集合 - 点的集合最大是多少. 题解:裸的最大权闭合子图. 代码: #inclu ...
- Codeforces 939 D Love Rescue
Love Rescue 题意:Valya 和 Tolya 是一对情侣, 他们的T恤和头巾上都有小写字母,但是女朋友嫌弃男朋友上T恤上的字不和她的头巾上的字一样,就很生气, 然后来了一个魔法师, 它可以 ...
- Halloween treats HDU 1808 鸽巢(抽屉)原理
Halloween treats Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- Wamp 新增php版本 教程
a.php版本下载:https://windows.php.net/download b.如果是apache环境下请认准 Thread Safe 版本 下载解压zip c.调整文件名为 php7. ...
- Python 70行代码实现简单算式计算器
描述:用户输入一系列算式字符串,程序返回计算结果. 要求:不使用eval.exec函数. 实现思路:找到当前字符串优先级最高的表达式,在算术运算中,()优先级最高,则取出算式最底层的(),再进行加减乘 ...
- MySQL索引原理及SQL优化
目录 索引(Index) 索引的原理 b+树 MySQL如何使用索引 如何优化 索引虽好,不可滥用 如何验证索引使用情况? SQL优化 explain查询执行计划 id select_type tab ...
- TestNG(八) 类分组测试
package com.course.testng.groups; import org.testng.annotations.Test; @Test(groups = "stu" ...
- 007:CSS字体图标
目录 理论 一:字体图标 图片是有诸多优点的,但是缺点很明显,比如图片不但增加了总文件的大小,还增加了很多额外的"http请求",这都会大大降低网页的性能的.更重要的是图片不能很好 ...