Android组件化点滴- APT生成路由动态参数类型文件

518 阅读3分钟

常规接收参数

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功能,首先把伪代码写下来,然后再通过注解处理器来生成文件。