如何优雅的处理onActivityResult:ResultDi

2020-09-22  本文已影响0人  正在吃饭的晓雨

最近学习到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发布的版本引入不了啊。呜呜呜呜

上一篇下一篇

猜你喜欢

热点阅读