前言
这段时间看公司项目涉及到注解,就抽时间学习了一下。查阅资料中,发现Benny老师的讲解非常到位,看完视频后根据印象仿写一个注解实现startActivity并传递参数、解析参数。附:
Benny老师的教学视频地址:https://www.bilibili.com/video/av32905508/?p=1
本文demo地址:https://github.com/liulianshanzhu/annotation
需求
实现自定义注解,启动activity并传递参数,参数分为必填参数和非必填参数,注解的参数自动解析数据,实现intent.putExtra()和intent.getExtra()等重复代码段。Required要求在创建构造器对象时必须填写,Optional可以通过Builder链式结构传入数值。
搭建环境
创建项目后,先创建3个moudle:annotations、compiler、runtime。分别存放自定义注解、注解的代码生成、以及依赖的运行环境,runtime一般可以不要。
-
annotations
自定义Builder,用于修饰Activity、和参数。其中Required修饰的参数最终生成的代码必须通过构造器传入,Optional可以通过链式结构选填
//Builder 修饰Activity
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Builder {
}
//Required 修饰必填的参数
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Required {
}
//Optional 修饰可选参数,可以根据需求规定默认类型参数
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Optional {
String stringValue() default "";
int intValue() default 0;
//other type
}
@Retention指注解的存活时间,有RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME。
- SOURCE:注解记录在类文件中,运行时vm不需要保留注释;
- CLASS:注解只保留到编译器编译时,并不会被加载到JVM中;
- RUNTIME:注解保留到程序运行时,会被加载到JVM中。
@Target表示注解修饰的类型,有:FIELD、CONSTRUCTOR、METHOD、TYPE、PARAMETER、PACKAGE等,大家根据英文翻译应该都能猜出他们各自的用处,这里不做详解。
-
compiler
compiler模块用于编写注解的代码生成逻辑。自定义BuilderProcessor继承AbstractProcessor。为了能够让编辑器识别,我们必须在java同级目录下创建:resources/META_INF/services/javax.annotation.processing.Processor文件,并将BuilderProcessor的引入填写进去。BuilderProcessor主要重写以下几个方法:
1、getSupportedAnnotationTypes() 我们需要解析的注解类型数组,以上有Builder、Required·和Optional,代码如下:
private val supportedAnnotations = setOf(Builder::class.java, Required::class.java, Optional::class.java)
override fun getSupportedAnnotationTypes() = supportedAnnotations.mapTo(HashSet<String>(), Class<*>::getCanonicalName)
2、init(p0: ProcessingEnvironment) 我们可以在这里获取得到注解所需要的一些参数,如:
- Elements:element的处理工具类。element指一系列与之相关的接口集合:PackageElement(包程序元素)、TypeElement(类或接口程序元素)、VariableElement(字段)等。
- Types:处理TypeMirror的工具类。Element只是一种语言元素,本身并不包含信息,而TypeMirror包含我们需要的注解信息。
- Filer:创建类、相关文件的工具类。
- Messager:开发者日志工具。
3、getSupportedSourceVersion() 当前注解支持的最新的java版本。可返回SourceVersion.latestSupported()。
4、process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 代码生成的逻辑都在此方法内实现,以下着重讲解。RoundEnvironment 参数可以获取详细的注解信息,以此解析每个注解,根据其规定的特性通过Filer输出相关的文件。
-
解析注解
override fun process(annotations: MutableSet<out TypeElement>, env: RoundEnvironment): Boolean {
val activityClasses = HashMap<Element, ActivityClass>()
//解析Builder注解,并拿到相关的activity信息
env.getElementsAnnotatedWith(Builder::class.java)
.filter { it.kind.isClass }//筛选去除抽象类,防止误调用
.forEach {element ->
try {
if (element.asType().isSubTypeOf("android.app.Activity")) {
activityClasses[element] = ActivityClass(element as TypeElement)
} else {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Unsupport typeElement")
}
}catch (e: Exception){
mMessager.printMessage(Diagnostic.Kind.ERROR, "Unable to parse ${Builder::class.java} binding.\n${e.message}")
}
}
//解析参数的注解,将其关联到相应的ActivityClass类对象中
env.getElementsAnnotatedWith(Required::class.java)
.filter { it.kind == ElementKind.FIELD } //该注解只修饰成员变量
.forEach {element ->
activityClasses[element.enclosingElement]?.fields?.add(Field(element as Symbol.VarSymbol))
?: mMessager.printMessage(Diagnostic.Kind.ERROR, "Field $element annotated as Required while ${element.enclosingElement} not annotated.")
}
env.getElementsAnnotatedWith(Optional::class.java)
.filter { it.kind == ElementKind.FIELD } //该注解只修饰成员变量
.forEach {element ->
activityClasses[element.enclosingElement]?.fields?.add(OptionalField(element as Symbol.VarSymbol))
?: mMessager.printMessage(Diagnostic.Kind.ERROR, "Field $element annotated as Required while ${element.enclosingElement} not annotated.")
}
activityClasses.values.map(ActivityClass::builder).forEach { it.build(mFiler) }
return true
}
其中ActivityClass为我们自定义的Activity类信息加载文件如下:
open class ActivityClass(typeElement: TypeElement) {
companion object {
val META_DATA = Class.forName("kotlin.Metadata") as Class<Annotation>
}
val simpleName: String = typeElement.simpleName.toString()
val packageName: String = typeElement.packageName()
//类存放的注解参数集合
val fields = TreeSet<Field>()
//用于生成代码模板
val builder: ActicityClassBuilder = ActicityClassBuilder (this)
val isAbstract = typeElement.modifiers.contains(Modifier.ABSTRACT) //是否是抽象类
val isKotlin = typeElement.getAnnotation(META_DATA) != null //每个kotlin文件都会有kotlin.Metadata的注解
}
Field是我们抽象出来的参数信息对象,必须实现Comparable<T>接口。
/**
* 注解的相关信息
*/
enum class FieldType{
Required, Optional
}
open class Field(val symbol: Symbol.VarSymbol): Comparable<Field> {
open val prefix = "Required_"
open val type = FieldType.Required
val name = symbol.qualifiedName.toString() //此类型元素的完全限定名称.required或者optional
val isPrivate = symbol.isPrivate //是否私有
val isPrimitive = symbol.type.isPrimitive //是否为基本类型
override fun compareTo(other: Field): Int {
return name.compareTo(other.name)
}
fun asJavaTypeName() = symbol.type.asJavaTypeName()
open fun asKotlinTypeName() = symbol.type.asKotlinTypeName()
}
-
自动生成代码
利用javapoet或者kotlinpoet可以轻松实现代码模板的编写,省去了手动导入类型引用填写包名等繁杂操作。
1、创建类文件
val typeBuilder = TypeSpec.classBuilder(activityClass.simpleName + BUILDER)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
//dosomething
try {
val file = JavaFile.builder(basicClass.packageName, typeBuilder.build()).build()
file.writeTo(filer)
} catch (e: IOException) {
e.printStackTrace()
}
2、构造私有并提供获取实例对象方法
/**
* 构造私有方法
*/
private fun constructor() {
val constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PRIVATE);
typeBuilder.addMethod(constructor.build())
}
/**
* 生成builder方法获取实例
*/
private fun builder() {
val requires = activityClass.fields.filter {
it.type == FieldType.Required
}
val builder = MethodSpec.methodBuilder("builder")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addStatement("${activityClass.simpleName + ActivityClassBuilder.BUILDER} builder = new ${activityClass.simpleName + ActivityClassBuilder.BUILDER}${"()"}")
requires.forEach { field ->
if (field.isPrimitive){
typeBuilder.addField(FieldSpec.builder(field.asJavaTypeName().box(), field.name, Modifier.PRIVATE).build())
}else {
typeBuilder.addField(FieldSpec.builder(field.asJavaTypeName(), field.name, Modifier.PRIVATE).build())
}
builder.addParameter(field.asJavaTypeName(), field.name)
.addStatement("builder.${field.name} = ${field.name}")
}
builder.returns(
ClassName.get(activityClass.packageName,
activityClass.simpleName + ActivityClassBuilder.BUILDER))
.addStatement("return builder")
typeBuilder.addMethod(builder.build())
}
3、可选参数的代码生成
/**
* 生成optional可选参数的赋值方法
*/
private fun optionalBuilder() {
val builderClassName =
ClassName.get(activityClass.packageName, activityClass.simpleName + ActivityClassBuilder.BUILDER)
activityClass.fields.filter { it.type == FieldType.Optional }.forEach { field ->
if (field.isPrimitive){
typeBuilder.addField(FieldSpec.builder(field.asJavaTypeName().box(), field.name, Modifier.PRIVATE).build())
}else {
typeBuilder.addField(FieldSpec.builder(field.asJavaTypeName(), field.name, Modifier.PRIVATE).build())
}
typeBuilder.addMethod(
MethodSpec.methodBuilder(field.name)
.addModifiers(Modifier.PUBLIC)
.addParameter(field.asJavaTypeName(), field.name)
.addStatement("this.${field.name} = ${field.name}")
.addStatement("return this")
.returns(builderClassName)
.build()
)
}
}
4、activity的启动以及参数的获取
/**
* 启动activity方法
*/
private fun startMethod() {
val builder = MethodSpec.methodBuilder("start")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(CONTEXT.java, "context")
builder.addStatement(
"\$T intent = new \$T(context, \$T.class)",
INTENT.java,
INTENT.java,
activityClass.typeElement
)
activityClass.fields.forEach { field ->
builder.beginControlFlow("if(${field.name} != null)")
.addStatement("intent.putExtra(\$S, \$L)", field.name, field.name)
.endControlFlow()
}
builder.addStatement("\$T.INSTANCE.startActivity(context, intent)", ACTIVITY_BUILDER.java)
typeBuilder.addMethod(builder.build())
}
/**
* 通过此方法获取extra
*/
private fun injectMethod() {
val injectMethodBuilder = MethodSpec.methodBuilder("inject")
.addParameter(ACTIVITY.java, "instance")
.addParameter(BUNDLE.java, "savedInstanceState")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.VOID)
.beginControlFlow("if(instance instanceof \$T)", activityClass.typeElement)
.addStatement("\$T typedInstance = (\$T) instance", activityClass.typeElement, activityClass.typeElement)
.addStatement("\$T extras = savedInstanceState == null ? typedInstance.getIntent().getExtras() : savedInstanceState", BUNDLE.java)
.beginControlFlow("if(extras != null)")
activityClass.fields.forEach { field ->
val name = field.name
val typeName = field.asJavaTypeName().box()
if(field is OptionalField){
injectMethodBuilder.addStatement("\$T \$LValue = \$T.<\$T>get(extras, \$S, \$L)", typeName, name, BUNDLE_UTILS.java, typeName, name, field.defaultValue)
} else {
injectMethodBuilder.addStatement("\$T \$LValue = \$T.<\$T>get(extras, \$S)", typeName, name, BUNDLE_UTILS.java, typeName, name)
}
if(field.isPrivate){
injectMethodBuilder.addStatement("typedInstance.set\$L(\$LValue)", name.capitalize(), name)
} else {
injectMethodBuilder.addStatement("typedInstance.\$L = \$LValue", name, name)
}
}
injectMethodBuilder.endControlFlow().endControlFlow()
typeBuilder.addMethod(injectMethodBuilder.build())
}
由于compiler是java库工程,没有context和intent等相关参数,可以通过java反射获取,demo中ClassType就是相关的解析方法。
结语
由于篇幅的限制,并未能很详细的说明,只是大概的讲述实现的流程,仅作为本人当前探索的记录。如果想学可以参考前言附带的Benny老师的视频地址。谢谢观看。