失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Kotlin编译时注解 简单实现ButterKnife

Kotlin编译时注解 简单实现ButterKnife

时间:2023-07-26 12:36:01

相关推荐

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.Viewclass 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对应,修改时注意同步修改等

有用的话加个关注哦!!!

代码

来源:/content-4-500351.html

如果觉得《Kotlin编译时注解 简单实现ButterKnife》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。