在 各個(gè) Activity 之間傳參 我們一般需要 使用 getIntent().get**Extra() 去獲取上個(gè)頁面的參數(shù),代碼比較重復(fù)拒逮,于是可以考慮像 Butterknife 那樣子去簡化 findViewById统阿。
考慮到代碼執(zhí)行效率問題玷氏,使用 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 "";
}
大致調(diào)用的時(shí)候 我們在 Activity 的 onCreate 里面調(diào)用
class MainActivity : AppCompatActivity() {
@Extra("msg")
var msg: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ExtrasInjector.inject(MainActivity_ExtraInjector(this))
}
}
MainActivity_ExtraInject 是 自動(dòng)生成的 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")
}
}
}
新建一個(gè) 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<TypeElement>, roundEnv: RoundEnvironment): Boolean {
val elements = roundEnv
.getElementsAnnotatedWith(Extra::class.java)
val map = HashMap<String, MutableList<Element>>()
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<String, MutableList<Element>>, list: Set<Element>) {
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<String, MutableList<Element>>) {
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<String> {
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())
}
}
這里先將注解的地方分類暇昂,同個(gè)文件里面是一起生成的同個(gè)文件撇眯,然后 可以設(shè)置 default value 這里我直接用字符串代替真慢,比較通用吧僧须。
工程 build 之后應(yīng)該就能看到生成的 Injector 文件了烁挟。
建議 對照生成的代碼文件 跟 ExtraInjectorProcessor 里面的代碼對比 應(yīng)該更好體會(huì)理解雕薪。