常规接收参数
class MainActivity : AppCompatActivity() {
lateinit var user: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
user = intent.getStringExtra("user") ?: ""
}
}
常规接收参数的方式就是在onCreate,使用intent.getXXX的方式,这是一个重复简单的事情,却又很重要。对于重复的事,我们可以使用APT来解决。
APT生成文件
每次在使用APT处理之前,都要明确需要生成的文件的样子。
MainActivityParameter.java APT生成的文件
public class MainActivityParameter implements ParameterLoad {
@Override
public void loadParameter(Object target) {
MainActivity t = (MainActivity)target;
t.user = t.getIntent().getStringExtra("user");
}
}
MainActivityParameter实现ParameterLoad接口的loadParameter,用于初始化MainActivity所需的参数。
ParameterLoad.java APT生成的文件所需要实现的接口
public interface ParameterLoad {
/**
* 目标对象.属性名 = getIntent().属性类型("注解值or属性名");完成赋值
*
* @param target 目标对象,如:MainActivity(中的某些属性)
*/
void loadParameter(Object target);
}
有了MainActivityParameter之后,就可以在MainActivity中使用了。
class MainActivity : AppCompatActivity() {
lateinit var user: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MainActivityParameter().loadParameter(this)
}
}
在onCreate调用MainActivityParameter().loadParameter(this)后,就会初始化user属性。
定义注解
有了APT要生成的文件,就可以开始干了,先定义属性注解。
@Target(AnnotationTarget.FIELD) // 该注解作用在属性之上
@kotlin.annotation.Retention(AnnotationRetention.BINARY)
annotation class Parameter(
// 不填写name的注解值表示该属性名就是key,填写了就用注解值作为key
val name: String = ""
)
定义注解处理器
使用注解处理器需要添加auto-service依赖,通过java面向对象的方法生成文件需要javapoet,
最后依赖如下:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//注解处理器
kapt 'com.google.auto.service:auto-service:1.0-rc6'
api 'com.google.auto.service:auto-service:1.0-rc6'
// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.9.0"
// 引入annotation,让注解处理器-处理注解
implementation project(':annotation')
}
ParameterProcessor.java
//注册注解处理器 Processor::class
@AutoService(Processor::class)
//注册需要处理哪个注解
@SupportedAnnotationTypes("com.loveqrc.annotation.Parameter")
//指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//定义处理器接收参数的可以
// 在build.gradle文件中配置
// android {
// defaultConfig {
// kapt {
// arguments {
// arg("content", project.getName())
// }
// }
// }
// }
//
@SupportedOptions("content")
class ParameterProcessor : AbstractProcessor() {
// 操作Element工具类 (类、函数、属性都是Element)
lateinit var elementUtils: Elements
//type(类信息)工具类,包含用于操作TypeMirror的工具方法
lateinit var typeUtils: Types
// Messager用来报告错误,警告和其他提示信息
lateinit var messager: Messager
// 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
lateinit var filer: Filer
// 临时map存储,用来存放被@Parameter注解的属性集合,生成类文件时遍历
// key:类节点, value:被@Parameter注解的属性集合
private val tempParameterMap: HashMap<TypeElement, MutableList<Element>> = HashMap()
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
elementUtils = processingEnv.elementUtils
typeUtils = processingEnv.typeUtils
messager = processingEnv.messager
filer = processingEnv.filer
val content: String? = processingEnv.options["content"]
messager.printMessage(Diagnostic.Kind.NOTE, content ?: "content is null")
}
/**
* 处理注解的入口,相当于main函数
*
* @param annotations 因为注解处理器可以处理多个注解,所以这里返回的是数组
* @param roundEnv 当前或是之前的运行环境,可以通过该对象查找找到的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
override fun process(
annotations: MutableSet<out TypeElement>,
roundEnv: RoundEnvironment
): Boolean {
if (annotations.isEmpty()) {
return false
}
//获取被@Parameter注解的元素(属性)
val elements = roundEnv.getElementsAnnotatedWith(Parameter::class.java)
if (elements.isEmpty()) {
return false
}
//获取注解的值,将其存起来
valueOfParameterMap(elements)
//生成对应的类文件
createParameterFile()
return true
}
private fun createParameterFile() {
if (tempParameterMap.isEmpty()) {
return
}
//通过Element工具类,获取对应的类型
val activityType = elementUtils.getTypeElement(Constants.ACTIVITY) as TypeElement
//获取要实现的接口的类型
val parameterType = elementUtils.getTypeElement(Constants.PARAMETER_LOAD) as TypeElement
// 配置loadParameter方法的(Object target)参数
val parameterSpec: ParameterSpec =
ParameterSpec.builder(TypeName.OBJECT, Constants.PARAMETER_NAME).build()
//每一个entry对应一个Activity
tempParameterMap.entries.forEach {
val typeElement = it.key
//如果不是在Activity中使用,那么就没意义了
if (!typeUtils.isSubtype(typeElement.asType(), activityType.asType())) {
throw RuntimeException("@Parameter only support for activity")
}
//获取类名
val className = ClassName.get(typeElement)
// 方法体内容构建
val factory = ParameterFactory.Builder(parameterSpec)
.setMessager(messager)
.setClassName(className)
.build()
// 添加方法体内容的第一行
//MainActivity t = (MainActivity) target;
factory.addFirstStatement()
// 遍历类里面所有属性
for (fieldElement in it.value) {
factory.buildStatement(fieldElement)
}
// 最终生成的类文件名(类名Parameter)
val finalClassName = typeElement.simpleName.toString() + Constants.PARAMETER_FILE_NAME
messager.printMessage(
Diagnostic.Kind.NOTE, "APT生成获取参数类文件:" +
className.packageName() + "." + finalClassName
)
// MainActivity$$Parameter
JavaFile.builder(
className.packageName(), // 包名
TypeSpec.classBuilder(finalClassName) // 类名
.addSuperinterface(ClassName.get(parameterType)) // 实现ParameterLoad接口
.addModifiers(Modifier.PUBLIC) // public修饰符
.addMethod(factory.build()) // 方法的构建(方法参数 + 方法体)
.build()
) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer) // 文件生成器开始生成类文件
}
}
private fun valueOfParameterMap(elements: MutableSet<out Element>) {
elements.forEach {
messager.printMessage(Diagnostic.Kind.NOTE, it.simpleName)
//获取当前element的父类
val enclosingElement = it.enclosingElement as TypeElement
// example:
//class MainActivity : AppCompatActivity() {
// @Parameter
// var user: String? = null
// }
// it : user VariableElement
// enclosingElement : MainActivity TypeElement
messager.printMessage(Diagnostic.Kind.NOTE, enclosingElement.simpleName)
if (tempParameterMap.containsKey(enclosingElement)) {
tempParameterMap[enclosingElement]!!.add(it)
} else {
val fields = ArrayList<Element>()
fields.add(it)
tempParameterMap[enclosingElement] = fields
}
}
}
}
ParameterProcessor处理的逻辑很简单,首先进行注册Processor,然后扫描有被@Parameter定义的属性,然后根据所在的类存到map中。存储完成后,通过javapoet生成文件。ParameterFactory是封装的工具类,
最后使用
@Parameter
lateinit var user: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
TestActivityParameter().loadParameter(this)
Log.e("TestActivity", "user:$user")
}
总结
APT适合解决经常写模板代码的场景,如要写APT功能,首先把伪代码写下来,然后再通过注解处理器来生成文件。