在 各个 Activity 之间传参 我们一般需要 使用 getIntent().get**Extra() 去获取上个页面的参数,代码比较重复,于是可以考虑像 Butterknife 那样子去简化 findViewById。
考虑到代码执行效率问题,使用 kotlinpoet 处理并生成 Kotlin 代码
代码如下:
//package com.android.extrainjector
interface IExtraInjector {
fun inject()
}
class ExtrasInjector {
companion object {
@JvmStatic
fun inject(injector: IExtraInjector) {
injector.inject()
}
}
}
//注解声明
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Extra {
String value() default "";
String defaultValue() default "";
}
大致调用的时候 我们在 Activity 的 onCreate 里面调用
class MainActivity : AppCompatActivity() {
@Extra("msg")
var msg: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ExtrasInjector.inject(MainActivity_ExtraInjector(this))
}
}
MainActivity_ExtraInject 是 自动生成的 Kotlin 代码文件。
class MainActivity_ExtraInjector(target: Any) : IExtraInjector {
var target: Any? = null
init {
this.target = target
}
override fun inject() {
if (target is MainActivity) {
val t = target as MainActivity
t.msg = t.getIntent().getStringExtra("msg")
}
}
}
新建一个 Java Lib 库,命名 compiler 存放 ExtraInjectorProcessor,然后记得在声明 ExtraInjectorProcessor
class ExtraInjectorProcessor : AbstractProcessor() {
private var elementUtils : Elements? = null
@Synchronized
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
elementUtils = processingEnv.elementUtils
}
override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean {
val elements = roundEnv
.getElementsAnnotatedWith(Extra::class.java)
val map = HashMap>()
category(map, elements)
if (map.size > 0) {
generateCode(map)
}
return true
}
private fun isSubClass(element: Element, type: String): Boolean{
return processingEnv.typeUtils.isSubtype(element.asType(), elementUtils!!
.getTypeElement(type).asType())
}
private fun category(map : HashMap>, list: Set) {
list.forEach {
if (it.kind.isField) {
val parent = it.enclosingElement
if (isSubClass(parent, "android.app.Activity")) {
val key = "${(parent as TypeElement).asClassName().packageName}.${parent.simpleName}"
var value = map[key]
if (value == null) {
value = ArrayList()
}
value.add(it)
map[key] = value
}
}
}
}
private fun generateCode(map : HashMap>) {
map.forEach { key, value ->
val constructMethod = FunSpec.constructorBuilder()
.addParameter(ParameterSpec.builder("target", Any::class).build())
.addStatement("this.target = target")
.build()
val parentElement = elementUtils!!.getTypeElement(key)
val className = key.split(".").last()
val loadMethodBuilder = FunSpec.builder("inject")
.addModifiers(KModifier.OVERRIDE)
.beginControlFlow("if (target is %T)", parentElement)
.addStatement("val t = target as %T", parentElement)
value.forEach {
autowear(loadMethodBuilder, it)
}
loadMethodBuilder.endControlFlow()
val codePackageName = elementUtils!!.getPackageOf(parentElement)
.qualifiedName.toString()
val generateClassName = "${className}_ExtraInjector"
val generateFileName = "${className}_ExtraInjector"
val file = FileSpec.builder(codePackageName, generateFileName)
.addType(TypeSpec.classBuilder(generateClassName)
.addKdoc("Generated by ExtraInjectorProcessor. Do not edit it!\n")
.primaryConstructor(constructMethod)
.addSuperinterface(ClassName("com.android.extrainjector", "IExtraInjector"))
.addProperty(PropertySpec.varBuilder("target", Any::class.asTypeName().asNullable())
.initializer("null")
.build())
.addFunction(loadMethodBuilder.build())
.build())
.build()
try {
file.writeFile()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun autowear(method: FunSpec.Builder, element: Element) {
val extra = element.getAnnotation(Extra::class.java)
var name = extra.value
val field = element.simpleName
if (name.isEmpty())
name = field.toString()
var placeholder = ""
var defaultValue = extra.defaultValue
when {
element.asType().toString() == "java.lang.String" -> placeholder = "String"
isSubClass(element, "android.os.Parcelable") -> {
placeholder = "Parcelable"
}
(element.asType().toString().contains("java.util.List")) -> {
//method.addStatement("System.out.print(\$S)", "java.util.List")
placeholder = "ParcelableArrayList"
}
else ->
when(element.asType().kind.ordinal) {
TypeKind.INT.ordinal -> {
placeholder = "Int"
defaultValue = if (defaultValue.isEmpty())
", 0"
else
", $defaultValue"
}
TypeKind.BOOLEAN.ordinal -> {
placeholder = "Boolean"
defaultValue = if (defaultValue.isEmpty())
", false"
else
", $defaultValue"
}
TypeKind.DOUBLE.ordinal -> {
placeholder = "Double"
defaultValue = if (defaultValue.isEmpty())
", 0"
else
", $defaultValue"
}
TypeKind.FLOAT.ordinal -> {
placeholder = "Float"
defaultValue = if (defaultValue.isEmpty())
", 0F"
else
", $defaultValue"
}
}
}
var statement = ""
if (placeholder.isNotEmpty()) {
statement = "t.$field = t.getIntent().get${placeholder}Extra(\"$name\" $defaultValue)"
}
method.addStatement(statement, element.asType())
}
override fun getSupportedAnnotationTypes(): Set {
return setOf(Extra::class.java.canonicalName)
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
private fun FileSpec.writeFile() {
val kaptKotlinGeneratedDir = processingEnv.options["kapt.kotlin.generated"]
val outputFile = File(kaptKotlinGeneratedDir).apply {
mkdirs()
}
writeTo(outputFile.toPath())
}
}
这里先将注解的地方分类,同个文件里面是一起生成的同个文件,然后 可以设置 default value 这里我直接用字符串代替,比较通用吧。
工程 build 之后应该就能看到生成的 Injector 文件了。
建议 对照生成的代码文件 跟 ExtraInjectorProcessor 里面的代码对比 应该更好体会理解。