最近学习到java注解这块,自己也想写一个注解试试看,正好当时在写onActivityResult,想到这块是不是可以通过注解用编译器生成固定代码(if else 判断code),然后通过对象回调到被注解的方法。
代码已经上传到github,不想了解实现原理的童鞋可以直接滑到底部
ps:本篇文章代码及源码语言均为kotlin,为了方便,文章的源码被修改过,但与源码相差不大
首先定义注解
/**
*[requestCode] 请求值
* [resultCode] 返回值
*/
@Retention(AnnotationRetention.SOURCE)//注解的保留时期 因为我们是通过编译期生成,所以保留在源码阶段就好了
@Target(AnnotationTarget.FUNCTION)//只能被注解到方法上
annotation class ResultDispatch(val requestCode: Int, val resultCode: Int)
编写注解处理器
首先先获取到哪些方法被注解了,以及被注解的方法在什么地方
//储存注解的map key 可以理解为 文件路径,value就是这个文件内的所有ResultDispatch注解
mDispatcherElements: MutableMap<TypeElement, MutableSet<Element>>
//获取到所有被注解的方法,以及注解信息
roundEnvironment.getElementsAnnotatedWith(ResultDispatch::class.java)
//通过循环来处理这些信息
for (element in elements) {
//这个方法我是这么理解的,如果这个注解在方法的参数里,那么返回方法名等信息,
//如果被注解的是方法,那么返回的是类的全路径等信息,
//同理,如果注解到类上,那么返回包名
//ps:这个仅个人理解,如果不对,还请指正
val enclosingElement = element.enclosingElement
//这里须要判断一下,因为正常情况下 储存类信息的是element是TypeElement
if (enclosingElement is TypeElement) {
var set = mDispatcherElements[enclosingElement]
if (set == null) {
set = mutableSetOf()
mDispatcherElements[enclosingElement] = set
}
//添加到set里
if (!set.contains(element)) {
set.add(element)
}
} else {
continue
}
}
到这里注解已经被我们处理完并且储存起来,但是这样还不行,我们须要通过编译器生成我们的辅助类
这里使用的生成源码的框架叫做 javapoet 感兴趣的可以了解一下,很强大
for (enclosedElement in mDispatcherElements.keys) {
val mutableSet = mDispatcherElements[enclosedElement] ?: continue
//默认的三个参数 requestCode resultCode Intent
val parameters = mutableListOf(
ParameterSpec.builder(Int::class.java, "requestCode").build(),
ParameterSpec.builder(Int::class.java, "resultCode").build(),
ParameterSpec.builder(Class.forName("android.content.Intent"), "data")
.build()
)
//生成分发方法
//参数名 首字母小写
val name = enclosedElement.simpleName.toString().decapitalize(Locale.ROOT)
//创建 dispatch 方法
val methodBuilder = MethodSpec.methodBuilder("dispatch")
//修饰符为 public static
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//添加参数,类型为回调引用的对象
.addParameter(ParameterSpec.builder( ClassName.get(enclosedElement.asType()),name).build())
//添加创建好的固定默认参数
.addParameters(parameters)
//方法的返回值为void
.returns(TypeName.VOID)
//到这里方法已经创建好了,但是方法里还没有内容
val contentBuilder = StringBuilder()
val iterator = mutableSet.iterator()
while (iterator.hasNext()) {
val element = iterator.next()
val dispatcher = element.getAnnotation(ResultDispatch::class.java)
//这里判断一下被注解的方法有没有Intent的参数
//如果方法里有参数的话,toString 会是 方法名(类的全路径)
//没有的话是 方法名()
//所以这里判断一下有没有Intent,为了下面生成方法内容
val haveData = element.toString().contains("android.content.Intent")
//这里就是字符串的拼接
contentBuilder.append(
"if (requestCode == ${dispatcher.requestCode} && resultCode == ${dispatcher.resultCode}) {\n" +
"$name.${element.simpleName}(${if (haveData) "data" else ""});\n" +
"} ${if (iterator.hasNext()) "else" else ""} "
)
}
//这里把方法内容添加到方法里
methodBuilder.addStatement(CodeBlock.of(contentBuilder.toString()))
try {
//根据类名创建辅助类
val typeSpec =TypeSpec.classBuilder("${enclosedElement.simpleName}Dispatcher")
//类修饰符 final public
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
//添加方法
.addMethod(methodBuilder.build())
.build()
//获取文件并写入
val file = JavaFile.builder(getPackageName(enclosedElement), typeSpec).build()
file.writeTo(processingEnv.filer)
} catch (e: IOException) {
}
}
private fun getPackageName(typeElement: TypeElement): String {
return mElementsUtil.getPackageOf(typeElement).qualifiedName.toString()
}
到这里实现的代码就已经全部介绍完了,下面看一下怎么使用
如何使用
在github上发不了新版本,但是引用下载就同步失败了。知道是为什么,有小伙伴知道问题出在哪里的话方便告诉一下
将源码下载下来并导入到工程里,在需要的module里添加代码
apply plugin: 'kotlin-kapt'
dependencies {
implementation project(path: ':annotation')
kapt project(path: ':processor')
}
然后在Activity或者Fragment 里 定义方法
@ResultDispatch(10, 20)
fun testDispatcher() {
Log.d("Main", "回来了,没有Intent")
}
@ResultDispatch(10, 30)
fun testDispatcher(intent: Intent?) {
Log.d("Main", "wow,这是传递过来的信息${intent?.getStringExtra("text")}")
}
重写一下onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
MainActivityDispatcher.dispatch(this, requestCode, resultCode, data)
}
其中 MainActivityDispatcher
是框架自动生成的,看一下里面的内容
public final class MainActivityDispatcher {
public static void dispatch(MainActivity mainActivity, int requestCode, int resultCode,
Intent data) {
if (requestCode == 10 && resultCode == 20) {
mainActivity.testDispatcher();
} else if (requestCode == 10 && resultCode == 30) {
mainActivity.testDispatcher(data);
}
}
}
只有一个dispatch的方法,里面的code判断由编译器帮我们自动生成,并且判断了我们需不需要intent,是不是方便了许多?
其实原来打算 onActivityResult这个方法也是用编译器生成,但是功力不到家,只能换方法实现,哪位大佬知道方法请告知小弟一下(土下座)。。。
啊,还有,为什么github发布的版本引入不了啊。呜呜呜呜
网友评论