Annotation注解实现startActivity

本文介绍如何使用自定义注解简化Activity启动及参数传递过程,实现代码复用,减少intent.putExtra()等重复代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这段时间看公司项目涉及到注解,就抽时间学习了一下。查阅资料中,发现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

  1. SOURCE:注解记录在类文件中,运行时vm不需要保留注释;
  2. CLASS:注解只保留到编译器编译时,并不会被加载到JVM中;
  3. 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老师的视频地址。谢谢观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值